/*
 * HLSL parser
 *
 * Copyright 2008 Stefan Dösinger
 * Copyright 2012 Matteo Bruni for CodeWeavers
 * Copyright 2019-2020 Zebediah Figura 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
 */

%code requires
{

#include "hlsl.h"
#include <stdio.h>

#define HLSL_YYLTYPE struct vkd3d_shader_location

struct parse_fields
{
    struct hlsl_struct_field *fields;
    size_t count, capacity;
};

struct parse_initializer
{
    struct hlsl_ir_node **args;
    unsigned int args_count;
    struct hlsl_block *instrs;
    bool braces;
    struct vkd3d_shader_location loc;
};

struct parse_parameter
{
    struct hlsl_type *type;
    const char *name;
    struct hlsl_semantic semantic;
    struct hlsl_reg_reservation reg_reservation;
    uint32_t modifiers;
    struct parse_initializer initializer;
};

struct parse_colon_attributes
{
    struct hlsl_semantic semantic;
    struct hlsl_reg_reservation reg_reservation;
};

struct parse_array_sizes
{
    uint32_t *sizes; /* innermost first */
    unsigned int count;
};

struct parse_variable_def
{
    struct list entry;
    struct vkd3d_shader_location loc;

    char *name;
    struct parse_array_sizes arrays;
    struct hlsl_semantic semantic;
    struct hlsl_reg_reservation reg_reservation;
    struct parse_initializer initializer;
    struct hlsl_scope *annotations;

    struct hlsl_type *basic_type;
    uint32_t modifiers;
    struct vkd3d_shader_location modifiers_loc;

    struct hlsl_state_block **state_blocks;
    unsigned int state_block_count;
    size_t state_block_capacity;
};

struct parse_function
{
    struct hlsl_ir_function_decl *decl;
    struct hlsl_func_parameters parameters;
    struct hlsl_semantic return_semantic;
    bool first;
};

struct parse_if_body
{
    struct hlsl_block *then_block;
    struct hlsl_block *else_block;
};

enum parse_assign_op
{
    ASSIGN_OP_ASSIGN,
    ASSIGN_OP_ADD,
    ASSIGN_OP_SUB,
    ASSIGN_OP_MUL,
    ASSIGN_OP_DIV,
    ASSIGN_OP_MOD,
    ASSIGN_OP_LSHIFT,
    ASSIGN_OP_RSHIFT,
    ASSIGN_OP_AND,
    ASSIGN_OP_OR,
    ASSIGN_OP_XOR,
};

struct parse_attribute_list
{
    unsigned int count;
    const struct hlsl_attribute **attrs;
};

struct state_block_index
{
    bool has_index;
    unsigned int index;
};

}

%code provides
{

int yylex(HLSL_YYSTYPE *yylval_param, HLSL_YYLTYPE *yylloc_param, void *yyscanner);

}

%code
{

#define YYLLOC_DEFAULT(cur, rhs, n) (cur) = YYRHSLOC(rhs, !!n)

static void yyerror(YYLTYPE *loc, void *scanner, struct hlsl_ctx *ctx, const char *s)
{
    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX, "%s", s);
}

static struct hlsl_ir_node *node_from_block(struct hlsl_block *block)
{
    return block->value;
}

static struct hlsl_block *make_empty_block(struct hlsl_ctx *ctx)
{
    struct hlsl_block *block;

    if ((block = hlsl_alloc(ctx, sizeof(*block))))
        hlsl_block_init(block);
    return block;
}

static struct list *make_empty_list(struct hlsl_ctx *ctx)
{
    struct list *list;

    if ((list = hlsl_alloc(ctx, sizeof(*list))))
        list_init(list);
    return list;
}

static void destroy_block(struct hlsl_block *block)
{
    if (!block)
        return;

    hlsl_block_cleanup(block);
    vkd3d_free(block);
}

static void destroy_switch_cases(struct list *cases)
{
    hlsl_cleanup_ir_switch_cases(cases);
    vkd3d_free(cases);
}

static bool hlsl_types_are_componentwise_compatible(struct hlsl_ctx *ctx, struct hlsl_type *src,
        struct hlsl_type *dst)
{
    unsigned int k, count = hlsl_type_component_count(dst);

    if (count > hlsl_type_component_count(src))
        return false;

    for (k = 0; k < count; ++k)
    {
        struct hlsl_type *src_comp_type, *dst_comp_type;

        src_comp_type = hlsl_type_get_component_type(ctx, src, k);
        dst_comp_type = hlsl_type_get_component_type(ctx, dst, k);

        if ((src_comp_type->class != HLSL_CLASS_SCALAR || dst_comp_type->class != HLSL_CLASS_SCALAR)
                && !hlsl_types_are_equal(src_comp_type, dst_comp_type))
            return false;
    }
    return true;
}

static bool hlsl_types_are_componentwise_equal(struct hlsl_ctx *ctx, struct hlsl_type *src,
        struct hlsl_type *dst)
{
    unsigned int k, count = hlsl_type_component_count(src);

    if (count != hlsl_type_component_count(dst))
        return false;

    for (k = 0; k < count; ++k)
    {
        struct hlsl_type *src_comp_type, *dst_comp_type;

        src_comp_type = hlsl_type_get_component_type(ctx, src, k);
        dst_comp_type = hlsl_type_get_component_type(ctx, dst, k);

        if (!hlsl_types_are_equal(src_comp_type, dst_comp_type))
            return false;
    }
    return true;
}

static bool type_contains_only_numerics(const struct hlsl_type *type)
{
    unsigned int i;

    if (type->class == HLSL_CLASS_ARRAY)
        return type_contains_only_numerics(type->e.array.type);
    if (type->class == HLSL_CLASS_STRUCT)
    {
        for (i = 0; i < type->e.record.field_count; ++i)
        {
            if (!type_contains_only_numerics(type->e.record.fields[i].type))
                return false;
        }
        return true;
    }
    return hlsl_is_numeric_type(type);
}

static bool explicit_compatible_data_types(struct hlsl_ctx *ctx, struct hlsl_type *src, struct hlsl_type *dst)
{
    if (hlsl_is_numeric_type(src) && src->e.numeric.dimx == 1 && src->e.numeric.dimy == 1
            && type_contains_only_numerics(dst))
        return true;

    if (src->class == HLSL_CLASS_MATRIX && dst->class == HLSL_CLASS_MATRIX
            && src->e.numeric.dimx >= dst->e.numeric.dimx && src->e.numeric.dimy >= dst->e.numeric.dimy)
        return true;

    if ((src->class == HLSL_CLASS_MATRIX && src->e.numeric.dimx > 1 && src->e.numeric.dimy > 1)
            && hlsl_type_component_count(src) != hlsl_type_component_count(dst))
        return false;

    if ((dst->class == HLSL_CLASS_MATRIX && dst->e.numeric.dimy > 1)
            && hlsl_type_component_count(src) != hlsl_type_component_count(dst))
        return false;

    return hlsl_types_are_componentwise_compatible(ctx, src, dst);
}

static bool implicit_compatible_data_types(struct hlsl_ctx *ctx, struct hlsl_type *src, struct hlsl_type *dst)
{
    if (hlsl_is_numeric_type(src) != hlsl_is_numeric_type(dst))
        return false;

    if (hlsl_is_numeric_type(src))
    {
        /* Scalar vars can be converted to any other numeric data type */
        if (src->e.numeric.dimx == 1 && src->e.numeric.dimy == 1)
            return true;
        /* The other way around is true too */
        if (dst->e.numeric.dimx == 1 && dst->e.numeric.dimy == 1)
            return true;

        if (src->class == HLSL_CLASS_MATRIX || dst->class == HLSL_CLASS_MATRIX)
        {
            if (src->class == HLSL_CLASS_MATRIX && dst->class == HLSL_CLASS_MATRIX)
                return src->e.numeric.dimx >= dst->e.numeric.dimx && src->e.numeric.dimy >= dst->e.numeric.dimy;

            /* Matrix-vector conversion is apparently allowed if they have
            * the same components count, or if the matrix is 1xN or Nx1
            * and we are reducing the component count */
            if (src->class == HLSL_CLASS_VECTOR || dst->class == HLSL_CLASS_VECTOR)
            {
                if (hlsl_type_component_count(src) == hlsl_type_component_count(dst))
                    return true;

                if ((src->class == HLSL_CLASS_VECTOR || src->e.numeric.dimx == 1 || src->e.numeric.dimy == 1)
                        && (dst->class == HLSL_CLASS_VECTOR || dst->e.numeric.dimx == 1 || dst->e.numeric.dimy == 1))
                    return hlsl_type_component_count(src) >= hlsl_type_component_count(dst);
            }

            return false;
        }
        else
        {
            return src->e.numeric.dimx >= dst->e.numeric.dimx;
        }
    }

    if (src->class == HLSL_CLASS_NULL)
    {
        switch (dst->class)
        {
            case HLSL_CLASS_DEPTH_STENCIL_STATE:
            case HLSL_CLASS_DEPTH_STENCIL_VIEW:
            case HLSL_CLASS_PIXEL_SHADER:
            case HLSL_CLASS_RASTERIZER_STATE:
            case HLSL_CLASS_RENDER_TARGET_VIEW:
            case HLSL_CLASS_SAMPLER:
            case HLSL_CLASS_STRING:
            case HLSL_CLASS_TEXTURE:
            case HLSL_CLASS_UAV:
            case HLSL_CLASS_VERTEX_SHADER:
                return true;
            default:
                break;
        }
    }

    return hlsl_types_are_componentwise_equal(ctx, src, dst);
}

static void check_condition_type(struct hlsl_ctx *ctx, const struct hlsl_ir_node *cond)
{
    const struct hlsl_type *type = cond->data_type;

    if (type->class == HLSL_CLASS_ERROR)
        return;

    if (type->class > HLSL_CLASS_LAST_NUMERIC || type->e.numeric.dimx > 1 || type->e.numeric.dimy > 1)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, type)))
            hlsl_error(ctx, &cond->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Condition type '%s' is not a scalar numeric type.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }
}

static struct hlsl_ir_node *add_cast(struct hlsl_ctx *ctx, struct hlsl_block *block,
        struct hlsl_ir_node *node, struct hlsl_type *dst_type, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *src_type = node->data_type;

    if (hlsl_types_are_equal(src_type, dst_type))
        return node;

    if (src_type->class == HLSL_CLASS_NULL)
        return node;

    return hlsl_block_add_cast(ctx, block, node, dst_type, loc);
}

static struct hlsl_ir_node *add_implicit_conversion(struct hlsl_ctx *ctx, struct hlsl_block *block,
        struct hlsl_ir_node *node, struct hlsl_type *dst_type, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *src_type = node->data_type;

    if (hlsl_types_are_equal(src_type, dst_type))
        return node;

    if (node->type == HLSL_IR_SAMPLER_STATE && dst_type->class == HLSL_CLASS_SAMPLER)
        return node;

    if (implicit_compatible_data_types(ctx, src_type, dst_type))
    {
        if (hlsl_is_numeric_type(dst_type) && hlsl_is_numeric_type(src_type)
                && dst_type->e.numeric.dimx * dst_type->e.numeric.dimy < src_type->e.numeric.dimx * src_type->e.numeric.dimy
                && ctx->warn_implicit_truncation)
            hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_IMPLICIT_TRUNCATION, "Implicit truncation of %s type.",
                    src_type->class == HLSL_CLASS_VECTOR ? "vector" : "matrix");
    }
    else
    {
        struct vkd3d_string_buffer *src_string, *dst_string;

        src_string = hlsl_type_to_string(ctx, src_type);
        dst_string = hlsl_type_to_string(ctx, dst_type);
        if (src_string && dst_string)
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Can't implicitly convert from %s to %s.", src_string->buffer, dst_string->buffer);
        hlsl_release_string_buffer(ctx, src_string);
        hlsl_release_string_buffer(ctx, dst_string);
    }

    return add_cast(ctx, block, node, dst_type, loc);
}

static void add_explicit_conversion(struct hlsl_ctx *ctx, struct hlsl_block *block,
        struct hlsl_type *dst_type, const struct parse_array_sizes *arrays, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *instr = node_from_block(block);
    struct hlsl_type *src_type = instr->data_type;
    unsigned int i;

    for (i = 0; i < arrays->count; ++i)
    {
        if (arrays->sizes[i] == HLSL_ARRAY_ELEMENTS_COUNT_IMPLICIT)
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Implicit size arrays not allowed in casts.");
            dst_type = ctx->builtin_types.error;
            break;
        }
        dst_type = hlsl_new_array_type(ctx, dst_type, arrays->sizes[i], HLSL_ARRAY_GENERIC);
    }

    if (instr->data_type->class == HLSL_CLASS_ERROR)
        return;

    if (!explicit_compatible_data_types(ctx, src_type, dst_type))
    {
        struct vkd3d_string_buffer *src_string, *dst_string;

        src_string = hlsl_type_to_string(ctx, src_type);
        dst_string = hlsl_type_to_string(ctx, dst_type);
        if (src_string && dst_string)
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Can't cast from %s to %s.",
                    src_string->buffer, dst_string->buffer);
        hlsl_release_string_buffer(ctx, src_string);
        hlsl_release_string_buffer(ctx, dst_string);
    }

    add_cast(ctx, block, instr, dst_type, loc);
}

static uint32_t add_modifiers(struct hlsl_ctx *ctx, uint32_t modifiers, uint32_t mod,
        const struct vkd3d_shader_location *loc)
{
    if (modifiers & mod)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_modifiers_to_string(ctx, mod)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                    "Modifier '%s' was already specified.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return modifiers;
    }
    return modifiers | mod;
}

static void append_conditional_break(struct hlsl_ctx *ctx, struct hlsl_block *cond_block)
{
    struct hlsl_ir_node *condition, *cast, *not;
    struct hlsl_block then_block;
    struct hlsl_type *bool_type;

    /* E.g. "for (i = 0; ; ++i)". */
    if (list_empty(&cond_block->instrs))
        return;

    condition = node_from_block(cond_block);

    check_condition_type(ctx, condition);

    bool_type = hlsl_get_scalar_type(ctx, HLSL_TYPE_BOOL);
    /* We already checked for a 1-component numeric type, so
     * add_implicit_conversion() is equivalent to add_cast() here. */
    cast = add_cast(ctx, cond_block, condition, bool_type, &condition->loc);
    not = hlsl_block_add_unary_expr(ctx, cond_block, HLSL_OP1_LOGIC_NOT, cast, &condition->loc);

    hlsl_block_init(&then_block);
    hlsl_block_add_jump(ctx, &then_block, HLSL_IR_JUMP_BREAK, NULL, &condition->loc);
    hlsl_block_add_if(ctx, cond_block, not, &then_block, NULL, HLSL_IF_FLATTEN_DEFAULT, &condition->loc);
}

static void check_attribute_list_for_duplicates(struct hlsl_ctx *ctx, const struct parse_attribute_list *attrs)
{
    unsigned int i, j;

    for (i = 0; i < attrs->count; ++i)
    {
        for (j = i + 1; j < attrs->count; ++j)
        {
            if (!strcmp(attrs->attrs[i]->name, attrs->attrs[j]->name))
                hlsl_error(ctx, &attrs->attrs[j]->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                        "Found duplicate attribute \"%s\".", attrs->attrs[j]->name);
        }
    }
}

static void resolve_loop_continue(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_loop_type type, struct hlsl_block *cond)
{
    struct hlsl_ir_node *instr, *next;

    LIST_FOR_EACH_ENTRY_SAFE(instr, next, &block->instrs, struct hlsl_ir_node, entry)
    {
        if (instr->type == HLSL_IR_IF)
        {
            struct hlsl_ir_if *iff = hlsl_ir_if(instr);

            resolve_loop_continue(ctx, &iff->then_block, type, cond);
            resolve_loop_continue(ctx, &iff->else_block, type, cond);
        }
        else if (instr->type == HLSL_IR_JUMP)
        {
            struct hlsl_ir_jump *jump = hlsl_ir_jump(instr);
            struct hlsl_block cond_block;

            if (jump->type != HLSL_IR_JUMP_UNRESOLVED_CONTINUE)
                continue;

            if (type == HLSL_LOOP_DO_WHILE)
            {
                if (!hlsl_clone_block(ctx, &cond_block, cond))
                    return;
                append_conditional_break(ctx, &cond_block);
                list_move_before(&instr->entry, &cond_block.instrs);
            }
        }
    }
}

static void check_loop_attributes(struct hlsl_ctx *ctx, const struct parse_attribute_list *attributes,
        const struct vkd3d_shader_location *loc)
{
    bool has_unroll = false, has_loop = false, has_fastopt = false;
    unsigned int i;

    for (i = 0; i < attributes->count; ++i)
    {
        const char *name = attributes->attrs[i]->name;

        has_loop |= !strcmp(name, "loop");
        has_unroll |= !strcmp(name, "unroll");
        has_fastopt |= !strcmp(name, "fastopt");
    }

    if (has_unroll && has_loop)
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX, "Unroll attribute can't be used with 'loop' attribute.");

    if (has_unroll && has_fastopt)
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX, "Unroll attribute can't be used with 'fastopt' attribute.");
}

static bool is_static_expression(struct hlsl_block *block)
{
    struct hlsl_ir_node *node;

    LIST_FOR_EACH_ENTRY(node, &block->instrs, struct hlsl_ir_node, entry)
    {
        switch (node->type)
        {
            case HLSL_IR_COMPILE:
            case HLSL_IR_CONSTANT:
            case HLSL_IR_EXPR:
            case HLSL_IR_SAMPLER_STATE:
            case HLSL_IR_STRING_CONSTANT:
            case HLSL_IR_SWIZZLE:
            case HLSL_IR_LOAD:
            case HLSL_IR_INDEX:
                continue;
            case HLSL_IR_STORE:
                if (hlsl_ir_store(node)->lhs.var->is_synthetic)
                    break;
                /* fall-through */
            case HLSL_IR_CALL:
            case HLSL_IR_IF:
            case HLSL_IR_INTERLOCKED:
            case HLSL_IR_LOOP:
            case HLSL_IR_JUMP:
            case HLSL_IR_RESOURCE_LOAD:
            case HLSL_IR_RESOURCE_STORE:
            case HLSL_IR_SWITCH:
            case HLSL_IR_STATEBLOCK_CONSTANT:
            case HLSL_IR_SYNC:
                return false;
        }
    }

    return true;
}

static struct hlsl_default_value evaluate_static_expression(struct hlsl_ctx *ctx,
        struct hlsl_block *block, struct hlsl_type *dst_type, const struct vkd3d_shader_location *loc)
{
    struct hlsl_default_value ret = {0};
    struct hlsl_ir_node *node;
    struct hlsl_block expr;
    struct hlsl_src src;

    if (node_from_block(block)->data_type->class == HLSL_CLASS_ERROR)
        return ret;

    if (!is_static_expression(block))
        hlsl_error(ctx, &node_from_block(block)->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                "Expected literal expression.");

    if (!hlsl_clone_block(ctx, &expr, &ctx->static_initializers))
        return ret;
    hlsl_block_add_block(&expr, block);

    node = add_implicit_conversion(ctx, &expr, node_from_block(&expr), dst_type, loc);

    /* Wrap the node into a src to allow the reference to survive the multiple const passes. */
    hlsl_src_from_node(&src, node);
    hlsl_lower_index_loads(ctx, &expr);
    hlsl_run_const_passes(ctx, &expr);
    node = src.node;
    hlsl_src_remove(&src);

    if (node->type == HLSL_IR_CONSTANT)
    {
        struct hlsl_ir_constant *constant = hlsl_ir_constant(node);

        ret.number = constant->value.u[0];
    }
    else if (node->type == HLSL_IR_STRING_CONSTANT)
    {
        struct hlsl_ir_string_constant *string = hlsl_ir_string_constant(node);

        if (!(ret.string = vkd3d_strdup(string->string)))
            return ret;
    }
    else if (node->type == HLSL_IR_COMPILE)
    {
        list_remove(&node->entry);
        ret.shader = hlsl_ir_compile(node);
    }
    else
    {
        hlsl_error(ctx, &node->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                "Failed to evaluate constant expression.");
    }

    hlsl_block_cleanup(&expr);

    return ret;
}

static unsigned int evaluate_static_expression_as_uint(struct hlsl_ctx *ctx, struct hlsl_block *block,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_default_value res;

    res = evaluate_static_expression(ctx, block, hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), loc);
    VKD3D_ASSERT(!res.string);
    return res.number.u;
}

static struct hlsl_block *create_loop(struct hlsl_ctx *ctx, enum hlsl_loop_type type,
        const struct parse_attribute_list *attributes, struct hlsl_block *init, struct hlsl_block *cond,
        struct hlsl_block *iter, struct hlsl_block *body, const struct vkd3d_shader_location *loc)
{
    enum hlsl_loop_unroll_type unroll_type = HLSL_LOOP_UNROLL;
    unsigned int i, unroll_limit = 0;

    check_attribute_list_for_duplicates(ctx, attributes);
    check_loop_attributes(ctx, attributes, loc);

    /* Ignore unroll(0) attribute, and any invalid attribute. */
    for (i = 0; i < attributes->count; ++i)
    {
        const struct hlsl_attribute *attr = attributes->attrs[i];
        if (!strcmp(attr->name, "unroll"))
        {
            if (attr->args_count > 1)
            {
                hlsl_warning(ctx, &attr->loc, VKD3D_SHADER_WARNING_HLSL_IGNORED_ATTRIBUTE,
                        "Ignoring 'unroll' attribute with more than 1 argument.");
                continue;
            }

            if (attr->args_count == 1)
            {
                struct hlsl_block expr;
                hlsl_block_init(&expr);
                if (!hlsl_clone_block(ctx, &expr, &attr->instrs))
                    return NULL;

                unroll_limit = evaluate_static_expression_as_uint(ctx, &expr, loc);
                hlsl_block_cleanup(&expr);
            }

            unroll_type = HLSL_LOOP_FORCE_UNROLL;
        }
        else if (!strcmp(attr->name, "loop"))
        {
            unroll_type = HLSL_LOOP_FORCE_LOOP;
        }
        else if (!strcmp(attr->name, "fastopt")
                || !strcmp(attr->name, "allow_uav_condition"))
        {
            hlsl_fixme(ctx, loc, "Unhandled attribute '%s'.", attr->name);
        }
        else
        {
            hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_UNKNOWN_ATTRIBUTE, "Unrecognized attribute '%s'.", attr->name);
        }
    }

    resolve_loop_continue(ctx, body, type, cond);

    if (!init && !(init = make_empty_block(ctx)))
        goto oom;

    append_conditional_break(ctx, cond);

    if (type == HLSL_LOOP_DO_WHILE)
        list_move_tail(&body->instrs, &cond->instrs);
    else
        list_move_head(&body->instrs, &cond->instrs);

    hlsl_block_add_loop(ctx, init, iter, body, unroll_type, unroll_limit, loc);

    destroy_block(cond);
    destroy_block(body);
    destroy_block(iter);
    return init;

oom:
    destroy_block(init);
    destroy_block(cond);
    destroy_block(iter);
    destroy_block(body);
    return NULL;
}

static unsigned int initializer_size(const struct parse_initializer *initializer)
{
    unsigned int count = 0, i;

    for (i = 0; i < initializer->args_count; ++i)
    {
        count += hlsl_type_component_count(initializer->args[i]->data_type);
    }
    return count;
}

static void cleanup_parse_attribute_list(struct parse_attribute_list *attr_list)
{
    unsigned int i = 0;

    VKD3D_ASSERT(attr_list);
    for (i = 0; i < attr_list->count; ++i)
        hlsl_free_attribute((struct hlsl_attribute *) attr_list->attrs[i]);
    vkd3d_free(attr_list->attrs);
}

static void free_parse_initializer(struct parse_initializer *initializer)
{
    destroy_block(initializer->instrs);
    vkd3d_free(initializer->args);
}

static struct hlsl_ir_node *get_swizzle(struct hlsl_ctx *ctx, struct hlsl_ir_node *value, const char *swizzle,
        struct vkd3d_shader_location *loc)
{
    unsigned int len = strlen(swizzle), component = 0;
    unsigned int i, set, swiz = 0;
    bool valid;

    if (value->data_type->class == HLSL_CLASS_MATRIX)
    {
        /* Matrix swizzle */
        struct hlsl_matrix_swizzle s;
        bool m_swizzle;
        unsigned int inc, x, y;

        if (len < 3 || swizzle[0] != '_')
            return NULL;
        m_swizzle = swizzle[1] == 'm';
        inc = m_swizzle ? 4 : 3;

        if (len % inc || len > inc * 4)
            return NULL;

        for (i = 0; i < len; i += inc)
        {
            if (swizzle[i] != '_')
                return NULL;
            if (m_swizzle)
            {
                if (swizzle[i + 1] != 'm')
                    return NULL;
                y = swizzle[i + 2] - '0';
                x = swizzle[i + 3] - '0';
            }
            else
            {
                y = swizzle[i + 1] - '1';
                x = swizzle[i + 2] - '1';
            }

            if (x >= value->data_type->e.numeric.dimx || y >= value->data_type->e.numeric.dimy)
                return NULL;
            s.components[component].x = x;
            s.components[component].y = y;
            component++;
        }
        return hlsl_new_matrix_swizzle(ctx, s, component, value, loc);
    }

    /* Vector swizzle */
    if (len > 4)
        return NULL;

    for (set = 0; set < 2; ++set)
    {
        valid = true;
        component = 0;
        for (i = 0; i < len; ++i)
        {
            char c[2][4] = {{'x', 'y', 'z', 'w'}, {'r', 'g', 'b', 'a'}};
            unsigned int s = 0;

            for (s = 0; s < 4; ++s)
            {
                if (swizzle[i] == c[set][s])
                    break;
            }
            if (s == 4)
            {
                valid = false;
                break;
            }

            if (s >= value->data_type->e.numeric.dimx)
                return NULL;
            hlsl_swizzle_set_component(&swiz, component++, s);
        }
        if (valid)
            return hlsl_new_swizzle(ctx, swiz, component, value, loc);
    }

    return NULL;
}

static bool add_return(struct hlsl_ctx *ctx, struct hlsl_block *block,
        struct hlsl_ir_node *return_value, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *return_type = ctx->cur_function->return_type;

    if (ctx->cur_function->return_var)
    {
        if (return_value)
        {
            if (return_value->data_type->class == HLSL_CLASS_ERROR)
                return true;

            return_value = add_implicit_conversion(ctx, block, return_value, return_type, loc);
            hlsl_block_add_simple_store(ctx, block, ctx->cur_function->return_var, return_value);
        }
        else
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RETURN, "Non-void functions must return a value.");
            return false;
        }
    }
    else
    {
        if (return_value)
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RETURN, "Void functions cannot return a value.");
    }

    hlsl_block_add_jump(ctx, block, HLSL_IR_JUMP_RETURN, NULL, loc);
    return true;
}

struct hlsl_ir_node *hlsl_add_load_component(struct hlsl_ctx *ctx, struct hlsl_block *block,
        struct hlsl_ir_node *var_instr, unsigned int comp, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_var *var;
    struct hlsl_deref src;

    if (!(var = hlsl_new_synthetic_var(ctx, "deref", var_instr->data_type, &var_instr->loc)))
    {
        block->value = ctx->error_instr;
        return ctx->error_instr;
    }

    hlsl_block_add_simple_store(ctx, block, var, var_instr);

    hlsl_init_simple_deref_from_var(&src, var);
    return hlsl_block_add_load_component(ctx, block, &src, comp, loc);
}

static void add_record_access(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *record,
        unsigned int idx, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *c;

    VKD3D_ASSERT(idx < record->data_type->e.record.field_count);

    c = hlsl_block_add_uint_constant(ctx, block, idx, loc);
    hlsl_block_add_index(ctx, block, record, c, loc);
}

static struct hlsl_ir_node *add_binary_arithmetic_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg1, struct hlsl_ir_node *arg2,
        const struct vkd3d_shader_location *loc);

static bool add_array_access(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *array,
        struct hlsl_ir_node *index, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *expr_type = array->data_type, *index_type = index->data_type;

    if (array->data_type->class == HLSL_CLASS_ERROR || index->data_type->class == HLSL_CLASS_ERROR)
    {
        block->value = ctx->error_instr;
        return true;
    }

    if ((expr_type->class == HLSL_CLASS_TEXTURE || expr_type->class == HLSL_CLASS_UAV)
            && expr_type->sampler_dim != HLSL_SAMPLER_DIM_GENERIC)
    {
        unsigned int dim_count = hlsl_sampler_dim_count(expr_type->sampler_dim);

        if (index_type->class > HLSL_CLASS_VECTOR || index_type->e.numeric.dimx != dim_count)
        {
            struct vkd3d_string_buffer *string;

            if ((string = hlsl_type_to_string(ctx, expr_type)))
                hlsl_error(ctx, &index->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "Array index of type '%s' must be of type 'uint%u'.", string->buffer, dim_count);
            hlsl_release_string_buffer(ctx, string);
            return false;
        }

        index = add_implicit_conversion(ctx, block, index,
                hlsl_get_vector_type(ctx, HLSL_TYPE_UINT, dim_count), &index->loc);
        hlsl_block_add_index(ctx, block, array, index, loc);
        return true;
    }

    if (index_type->class != HLSL_CLASS_SCALAR)
    {
        hlsl_error(ctx, &index->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Array index is not scalar.");
        return false;
    }

    index = hlsl_block_add_cast(ctx, block, index, hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), &index->loc);

    if (expr_type->class != HLSL_CLASS_ARRAY && expr_type->class != HLSL_CLASS_VECTOR && expr_type->class != HLSL_CLASS_MATRIX)
    {
        if (expr_type->class == HLSL_CLASS_SCALAR)
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_INDEX, "Scalar expressions cannot be array-indexed.");
        else
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_INDEX, "Expression cannot be array-indexed.");
        return false;
    }

    hlsl_block_add_index(ctx, block, array, index, loc);
    return true;
}

static const struct hlsl_struct_field *get_struct_field(const struct hlsl_struct_field *fields,
        size_t count, const char *name)
{
    size_t i;

    for (i = 0; i < count; ++i)
    {
        if (!strcmp(fields[i].name, name))
            return &fields[i];
    }
    return NULL;
}

static struct hlsl_type *apply_type_modifiers(struct hlsl_ctx *ctx, struct hlsl_type *type,
        uint32_t *modifiers, bool force_majority, const struct vkd3d_shader_location *loc)
{
    unsigned int default_majority = 0;
    struct hlsl_type *new_type;

    if (!(*modifiers & HLSL_MODIFIERS_MAJORITY_MASK)
            && !(type->modifiers & HLSL_MODIFIERS_MAJORITY_MASK)
            && type->class == HLSL_CLASS_MATRIX)
    {
        if (!(default_majority = ctx->matrix_majority) && force_majority)
            default_majority = HLSL_MODIFIER_COLUMN_MAJOR;
    }
    else if (type->class != HLSL_CLASS_MATRIX && (*modifiers & HLSL_MODIFIERS_MAJORITY_MASK))
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                "'row_major' and 'column_major' modifiers are only allowed for matrices.");
    }

    if (!default_majority && !(*modifiers & HLSL_TYPE_MODIFIERS_MASK))
        return type;

    if (!(new_type = hlsl_type_clone(ctx, type, default_majority, *modifiers & HLSL_TYPE_MODIFIERS_MASK)))
        return NULL;

    *modifiers &= ~HLSL_TYPE_MODIFIERS_MASK;

    if ((new_type->modifiers & HLSL_MODIFIER_ROW_MAJOR) && (new_type->modifiers & HLSL_MODIFIER_COLUMN_MAJOR))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                "'row_major' and 'column_major' modifiers are mutually exclusive.");

    return new_type;
}

static void free_parse_variable_def(struct parse_variable_def *v)
{
    free_parse_initializer(&v->initializer);
    vkd3d_free(v->arrays.sizes);
    vkd3d_free(v->name);
    hlsl_cleanup_semantic(&v->semantic);
    if (v->state_block_count)
    {
        for (unsigned int i = 0; i < v->state_block_count; ++i)
            hlsl_free_state_block(v->state_blocks[i]);
        vkd3d_free(v->state_blocks);
    }
    vkd3d_free(v);
}

static void destroy_parse_variable_defs(struct list *defs)
{
    struct parse_variable_def *v, *v_next;

    LIST_FOR_EACH_ENTRY_SAFE(v, v_next, defs, struct parse_variable_def, entry)
        free_parse_variable_def(v);
    vkd3d_free(defs);
}

static bool gen_struct_fields(struct hlsl_ctx *ctx, struct parse_fields *fields,
        struct hlsl_type *type, uint32_t modifiers, struct list *defs)
{
    struct parse_variable_def *v, *v_next;
    size_t i = 0;

    if (type->class == HLSL_CLASS_MATRIX)
        VKD3D_ASSERT(type->modifiers & HLSL_MODIFIERS_MAJORITY_MASK);

    memset(fields, 0, sizeof(*fields));
    fields->count = list_count(defs);
    if (!hlsl_array_reserve(ctx, (void **)&fields->fields, &fields->capacity, fields->count, sizeof(*fields->fields)))
        return false;

    LIST_FOR_EACH_ENTRY_SAFE(v, v_next, defs, struct parse_variable_def, entry)
    {
        struct hlsl_struct_field *field = &fields->fields[i++];
        bool unbounded_res_array = false;
        unsigned int k;

        field->type = type;

        if (hlsl_version_ge(ctx, 5, 1) && hlsl_type_is_resource(type))
        {
            for (k = 0; k < v->arrays.count; ++k)
                unbounded_res_array |= (v->arrays.sizes[k] == HLSL_ARRAY_ELEMENTS_COUNT_IMPLICIT);
        }

        if (unbounded_res_array)
        {
            if (v->arrays.count == 1)
            {
                hlsl_fixme(ctx, &v->loc, "Unbounded resource arrays as struct fields.");
                free_parse_variable_def(v);
                vkd3d_free(field);
                continue;
            }
            else
            {
                hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "Unbounded resource arrays cannot be multi-dimensional.");
            }
        }
        else
        {
            for (k = 0; k < v->arrays.count; ++k)
            {
                if (v->arrays.sizes[k] == HLSL_ARRAY_ELEMENTS_COUNT_IMPLICIT)
                {
                    hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Implicit size arrays not allowed in struct fields.");
                    field->type = ctx->builtin_types.error;
                    break;
                }

                field->type = hlsl_new_array_type(ctx, field->type, v->arrays.sizes[k], HLSL_ARRAY_GENERIC);
            }
        }

        if (hlsl_version_ge(ctx, 5, 1) && field->type->class == HLSL_CLASS_ARRAY && hlsl_type_is_resource(field->type))
            hlsl_fixme(ctx, &v->loc, "Shader model 5.1+ resource array.");

        vkd3d_free(v->arrays.sizes);
        field->loc = v->loc;
        field->name = v->name;
        field->semantic = v->semantic;
        field->storage_modifiers = modifiers | v->semantic.modifiers;
        if (v->initializer.args_count)
        {
            hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX, "Illegal initializer on a struct field.");
            free_parse_initializer(&v->initializer);
        }
        if (v->reg_reservation.offset_type)
            hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                "packoffset() is not allowed inside struct definitions.");
        vkd3d_free(v);
    }
    vkd3d_free(defs);
    return true;
}

static void add_record_access_recurse(struct hlsl_ctx *ctx, struct hlsl_block *block,
        const char *name, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *record = node_from_block(block);
    const struct hlsl_type *type = record->data_type;
    const struct hlsl_struct_field *field, *base;

    if (type->class == HLSL_CLASS_ERROR)
        return;

    if ((field = get_struct_field(type->e.record.fields, type->e.record.field_count, name)))
    {
        unsigned int field_idx = field - type->e.record.fields;

        add_record_access(ctx, block, record, field_idx, loc);
    }
    else if ((base = get_struct_field(type->e.record.fields, type->e.record.field_count, "$super")))
    {
        unsigned int base_idx = base - type->e.record.fields;

        add_record_access(ctx, block, record, base_idx, loc);
        add_record_access_recurse(ctx, block, name, loc);
    }
    else
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED, "Field \"%s\" is not defined.", name);
        block->value = ctx->error_instr;
    }
}

static bool add_typedef(struct hlsl_ctx *ctx, struct hlsl_type *const orig_type, struct list *list)
{
    struct parse_variable_def *v, *v_next;
    struct hlsl_type *type;
    unsigned int i;
    bool ret;

    LIST_FOR_EACH_ENTRY_SAFE(v, v_next, list, struct parse_variable_def, entry)
    {
        if (!v->arrays.count)
        {
            if (!(type = hlsl_type_clone(ctx, orig_type, 0, 0)))
            {
                free_parse_variable_def(v);
                continue;
            }
        }
        else
        {
            uint32_t var_modifiers = 0;

            if (!(type = apply_type_modifiers(ctx, orig_type, &var_modifiers, true, &v->loc)))
            {
                free_parse_variable_def(v);
                continue;
            }
        }

        ret = true;
        for (i = 0; i < v->arrays.count; ++i)
        {
            if (v->arrays.sizes[i] == HLSL_ARRAY_ELEMENTS_COUNT_IMPLICIT)
            {
                hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "Implicit size arrays not allowed in typedefs.");
                if (!(type = hlsl_type_clone(ctx, ctx->builtin_types.error, 0, 0)))
                {
                    free_parse_variable_def(v);
                    ret = false;
                }
                break;
            }

            if (!(type = hlsl_new_array_type(ctx, type, v->arrays.sizes[i], HLSL_ARRAY_GENERIC)))
            {
                free_parse_variable_def(v);
                ret = false;
                break;
            }
        }
        if (!ret)
            continue;
        vkd3d_free(v->arrays.sizes);

        vkd3d_free((void *)type->name);
        type->name = v->name;
        type->is_typedef = true;

        ret = hlsl_scope_add_type(ctx->cur_scope, type);
        if (!ret)
            hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_REDEFINED,
                    "Type '%s' is already defined.", v->name);
        free_parse_initializer(&v->initializer);
        vkd3d_free(v);
    }
    vkd3d_free(list);
    return true;
}

static void check_invalid_stream_output_object(struct hlsl_ctx *ctx, const struct hlsl_type *type,
        const char *name, const struct vkd3d_shader_location* loc)
{
    if (hlsl_type_component_count(type) != 1)
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Stream output object '%s' is not single-element.", name);
}

static void initialize_var_components(struct hlsl_ctx *ctx, struct hlsl_block *instrs,
        struct hlsl_ir_var *dst, unsigned int *store_index, struct hlsl_ir_node *src,
        bool is_default_values_initializer);

static bool add_func_parameter(struct hlsl_ctx *ctx, struct hlsl_func_parameters *parameters,
        struct parse_parameter *param, const struct vkd3d_shader_location *loc)
{
    uint32_t modifiers = param->modifiers | param->semantic.modifiers;
    struct hlsl_ir_var *var;

    if (param->type->class == HLSL_CLASS_MATRIX)
        VKD3D_ASSERT(param->type->modifiers & HLSL_MODIFIERS_MAJORITY_MASK);

    if ((modifiers & HLSL_STORAGE_OUT) && (modifiers & HLSL_STORAGE_UNIFORM))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                "Parameter '%s' is declared as both \"out\" and \"uniform\".", param->name);

    if ((modifiers & HLSL_STORAGE_OUT) && !(modifiers & HLSL_STORAGE_IN)
            && (param->type->modifiers & HLSL_MODIFIER_CONST))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                "Parameter '%s' is declared as both \"out\" and \"const\".", param->name);

    if (param->reg_reservation.offset_type)
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                "packoffset() is not allowed on function parameters.");

    if (parameters->count && parameters->vars[parameters->count - 1]->default_values
                && !param->initializer.args_count)
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_MISSING_INITIALIZER,
                "Missing default value for parameter '%s'.", param->name);

    if (param->initializer.args_count && (modifiers & HLSL_STORAGE_OUT))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                "Output parameter '%s' has a default value.", param->name);

    if (hlsl_get_stream_output_type(param->type))
        check_invalid_stream_output_object(ctx, param->type, param->name, loc);

    if (!(var = hlsl_new_var(ctx, param->name, param->type, loc, &param->semantic, modifiers,
            &param->reg_reservation)))
        return false;
    var->is_param = 1;

    if (param->initializer.args_count)
    {
        unsigned int component_count = hlsl_type_component_count(param->type);
        unsigned int store_index = 0;
        unsigned int size, i;

        if (!(var->default_values = hlsl_calloc(ctx, component_count, sizeof(*var->default_values))))
            return false;

        if (!param->initializer.braces)
        {
            add_implicit_conversion(ctx, param->initializer.instrs, param->initializer.args[0], param->type, loc);
            param->initializer.args[0] = node_from_block(param->initializer.instrs);
        }

        size = initializer_size(&param->initializer);
        if (component_count != size)
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                    "Expected %u components in initializer, but got %u.", component_count, size);
        }

        for (i = 0; i < param->initializer.args_count; ++i)
        {
            initialize_var_components(ctx, param->initializer.instrs, var,
                    &store_index, param->initializer.args[i], true);
        }

        free_parse_initializer(&param->initializer);
    }

    hlsl_add_var(ctx, var);

    if (!hlsl_array_reserve(ctx, (void **)&parameters->vars, &parameters->capacity,
            parameters->count + 1, sizeof(*parameters->vars)))
        return false;
    parameters->vars[parameters->count++] = var;
    return true;
}

static void add_pass(struct hlsl_ctx *ctx, const char *name, struct hlsl_scope *annotations,
        struct hlsl_state_block *state_block, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_var *var;
    struct hlsl_type *type;

    type = hlsl_get_type(ctx->globals, "pass", false, false);
    if (!(var = hlsl_new_var(ctx, name, type, loc, NULL, 0, NULL)))
        return;
    var->annotations = annotations;

    var->state_blocks = hlsl_alloc(ctx, sizeof(*var->state_blocks));
    var->state_blocks[0] = state_block;
    var->state_block_count = 1;
    var->state_block_capacity = 1;

    hlsl_add_var(ctx, var);
}

static void add_technique(struct hlsl_ctx *ctx, const char *name, struct hlsl_scope *scope,
        struct hlsl_scope *annotations, const char *typename, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_var *var;
    struct hlsl_type *type;

    type = hlsl_get_type(ctx->globals, typename, false, false);
    if (!(var = hlsl_new_var(ctx, name, type, loc, NULL, 0, NULL)))
        return;
    var->scope = scope;
    var->annotations = annotations;

    hlsl_add_var(ctx, var);
}

static void add_effect_group(struct hlsl_ctx *ctx, const char *name, struct hlsl_scope *scope,
        struct hlsl_scope *annotations, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_var *var;
    struct hlsl_type *type;

    type = hlsl_get_type(ctx->globals, "fxgroup", false, false);
    if (!(var = hlsl_new_var(ctx, name, type, loc, NULL, 0, NULL)))
        return;
    var->scope = scope;
    var->annotations = annotations;

    hlsl_add_var(ctx, var);
}

static bool parse_reservation_index(struct hlsl_ctx *ctx, const char *string, unsigned int bracket_offset,
        struct hlsl_reg_reservation *reservation)
{
    char *endptr;

    reservation->reg_type = ascii_tolower(string[0]);

    /* Prior to SM5.1, fxc simply ignored bracket offsets for 'b' types. */
    if (reservation->reg_type == 'b' && hlsl_version_lt(ctx, 5, 1))
    {
        bracket_offset = 0;
    }

    if (string[1] == '\0')
    {
        reservation->reg_index = bracket_offset;
        return true;
    }

    reservation->reg_index = strtoul(string + 1, &endptr, 10) + bracket_offset;

    if (*endptr)
    {
        /* fxc for SM >= 4 treats all parse failures for 'b' types as successes,
         * setting index to -1. It will later fail while validating slot limits. */
        if (reservation->reg_type == 'b' && hlsl_version_ge(ctx, 4, 0))
        {
            reservation->reg_index = -1;
            return true;
        }

        /* All other types tolerate leftover characters. */
        if (endptr == string + 1)
            return false;
    }

    return true;
}

static bool parse_reservation_space(const char *string, uint32_t *space)
{
    return !ascii_strncasecmp(string, "space", 5) && sscanf(string + 5, "%u", space);
}

static struct hlsl_reg_reservation parse_packoffset(struct hlsl_ctx *ctx, const char *reg_string,
        const char *swizzle, const struct vkd3d_shader_location *loc)
{
    struct hlsl_reg_reservation reservation = {0};
    char *endptr;

    if (hlsl_version_lt(ctx, 4, 0))
        return reservation;

    reservation.offset_index = strtoul(reg_string + 1, &endptr, 10);
    if (*endptr)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                "Invalid packoffset() syntax.");
        return reservation;
    }

    reservation.offset_type = ascii_tolower(reg_string[0]);
    if (reservation.offset_type != 'c')
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                "Only 'c' registers are allowed in packoffset().");
        return reservation;
    }

    reservation.offset_index *= 4;

    if (swizzle)
    {
        if (strlen(swizzle) != 1)
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                    "Invalid packoffset() component \"%s\".", swizzle);

        if (swizzle[0] == 'x' || swizzle[0] == 'r')
            reservation.offset_index += 0;
        else if (swizzle[0] == 'y' || swizzle[0] == 'g')
            reservation.offset_index += 1;
        else if (swizzle[0] == 'z' || swizzle[0] == 'b')
            reservation.offset_index += 2;
        else if (swizzle[0] == 'w' || swizzle[0] == 'a')
            reservation.offset_index += 3;
        else
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                    "Invalid packoffset() component \"%s\".", swizzle);
    }

    return reservation;
}

static struct hlsl_block *make_block(struct hlsl_ctx *ctx, struct hlsl_ir_node *instr)
{
    struct hlsl_block *block;

    if (!(block = make_empty_block(ctx)))
    {
        hlsl_free_instr(instr);
        return NULL;
    }
    hlsl_block_add_instr(block, instr);
    return block;
}

static bool expr_compatible_data_types(struct hlsl_type *t1, struct hlsl_type *t2)
{
    /* Scalar vars can be converted to pretty much everything */
    if ((t1->e.numeric.dimx == 1 && t1->e.numeric.dimy == 1) || (t2->e.numeric.dimx == 1 && t2->e.numeric.dimy == 1))
        return true;

    if (t1->class == HLSL_CLASS_VECTOR && t2->class == HLSL_CLASS_VECTOR)
        return true;

    if (t1->class == HLSL_CLASS_MATRIX || t2->class == HLSL_CLASS_MATRIX)
    {
        /* Matrix-vector conversion is apparently allowed if either they have the same components
           count or the matrix is nx1 or 1xn */
        if (t1->class == HLSL_CLASS_VECTOR || t2->class == HLSL_CLASS_VECTOR)
        {
            if (hlsl_type_component_count(t1) == hlsl_type_component_count(t2))
                return true;

            return (t1->class == HLSL_CLASS_MATRIX && (t1->e.numeric.dimx == 1 || t1->e.numeric.dimy == 1))
                    || (t2->class == HLSL_CLASS_MATRIX && (t2->e.numeric.dimx == 1 || t2->e.numeric.dimy == 1));
        }

        /* Both matrices */
        if ((t1->e.numeric.dimx >= t2->e.numeric.dimx && t1->e.numeric.dimy >= t2->e.numeric.dimy)
                || (t1->e.numeric.dimx <= t2->e.numeric.dimx && t1->e.numeric.dimy <= t2->e.numeric.dimy))
            return true;
    }

    return false;
}

static enum hlsl_base_type expr_common_base_type(enum hlsl_base_type t1, enum hlsl_base_type t2)
{
    if (t1 == t2)
        return t1 == HLSL_TYPE_BOOL ? HLSL_TYPE_INT : t1;
    if (t1 == HLSL_TYPE_DOUBLE || t2 == HLSL_TYPE_DOUBLE)
        return HLSL_TYPE_DOUBLE;
    if (t1 == HLSL_TYPE_FLOAT || t2 == HLSL_TYPE_FLOAT
            || t1 == HLSL_TYPE_HALF || t2 == HLSL_TYPE_HALF)
        return HLSL_TYPE_FLOAT;
    if (t1 == HLSL_TYPE_UINT || t2 == HLSL_TYPE_UINT)
        return HLSL_TYPE_UINT;
    if (t1 == HLSL_TYPE_INT || t2 == HLSL_TYPE_INT)
        return HLSL_TYPE_INT;
    if (t1 == HLSL_TYPE_MIN16UINT || t2 == HLSL_TYPE_MIN16UINT)
        return HLSL_TYPE_MIN16UINT;
    vkd3d_unreachable();
}

static bool expr_common_shape(struct hlsl_ctx *ctx, struct hlsl_type *t1, struct hlsl_type *t2,
        const struct vkd3d_shader_location *loc, enum hlsl_type_class *type, unsigned int *dimx, unsigned int *dimy)
{
    if (!hlsl_is_numeric_type(t1))
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, t1)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Expression of type \"%s\" cannot be used in a numeric expression.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    if (!hlsl_is_numeric_type(t2))
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, t2)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Expression of type \"%s\" cannot be used in a numeric expression.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    if (!expr_compatible_data_types(t1, t2))
    {
        struct vkd3d_string_buffer *t1_string = hlsl_type_to_string(ctx, t1);
        struct vkd3d_string_buffer *t2_string = hlsl_type_to_string(ctx, t2);

        if (t1_string && t2_string)
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Expression data types \"%s\" and \"%s\" are incompatible.",
                    t1_string->buffer, t2_string->buffer);
        hlsl_release_string_buffer(ctx, t1_string);
        hlsl_release_string_buffer(ctx, t2_string);
        return false;
    }

    if (t1->e.numeric.dimx == 1 && t1->e.numeric.dimy == 1)
    {
        *type = t2->class;
        *dimx = t2->e.numeric.dimx;
        *dimy = t2->e.numeric.dimy;
    }
    else if (t2->e.numeric.dimx == 1 && t2->e.numeric.dimy == 1)
    {
        *type = t1->class;
        *dimx = t1->e.numeric.dimx;
        *dimy = t1->e.numeric.dimy;
    }
    else if (t1->class == HLSL_CLASS_MATRIX && t2->class == HLSL_CLASS_MATRIX)
    {
        *type = HLSL_CLASS_MATRIX;
        *dimx = min(t1->e.numeric.dimx, t2->e.numeric.dimx);
        *dimy = min(t1->e.numeric.dimy, t2->e.numeric.dimy);
    }
    else
    {
        if (t1->e.numeric.dimx * t1->e.numeric.dimy <= t2->e.numeric.dimx * t2->e.numeric.dimy)
        {
            *type = t1->class;
            *dimx = t1->e.numeric.dimx;
            *dimy = t1->e.numeric.dimy;
        }
        else
        {
            *type = t2->class;
            *dimx = t2->e.numeric.dimx;
            *dimy = t2->e.numeric.dimy;
        }
    }

    return true;
}

static struct hlsl_ir_node *add_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS],
        struct hlsl_type *type, const struct vkd3d_shader_location *loc)
{
    unsigned int i;

    if (type->class == HLSL_CLASS_MATRIX)
    {
        struct hlsl_type *scalar_type;
        struct hlsl_deref var_deref;
        struct hlsl_ir_var *var;

        scalar_type = hlsl_get_scalar_type(ctx, type->e.numeric.type);

        if (!(var = hlsl_new_synthetic_var(ctx, "split_op", type, loc)))
            return NULL;
        hlsl_init_simple_deref_from_var(&var_deref, var);

        for (i = 0; i < type->e.numeric.dimy * type->e.numeric.dimx; ++i)
        {
            struct hlsl_ir_node *value, *cell_operands[HLSL_MAX_OPERANDS] = { NULL };
            unsigned int j;

            for (j = 0; j < HLSL_MAX_OPERANDS; j++)
            {
                if (operands[j])
                    cell_operands[j] = hlsl_add_load_component(ctx, block, operands[j], i, loc);
            }

            if (!(value = add_expr(ctx, block, op, cell_operands, scalar_type, loc)))
                return NULL;

            hlsl_block_add_store_component(ctx, block, &var_deref, i, value);
        }

        return hlsl_block_add_simple_load(ctx, block, var, loc);
    }

    return hlsl_block_add_expr(ctx, block, op, operands, type, loc);
}

static void check_integer_type(struct hlsl_ctx *ctx, const struct hlsl_ir_node *instr)
{
    const struct hlsl_type *type = instr->data_type;
    struct vkd3d_string_buffer *string;

    if (hlsl_type_is_integer(type))
        return;

    if ((string = hlsl_type_to_string(ctx, type)))
        hlsl_error(ctx, &instr->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Expression type '%s' is not integer.", string->buffer);
    hlsl_release_string_buffer(ctx, string);
}

static struct hlsl_ir_node *add_unary_arithmetic_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {arg};

    if (arg->data_type->class == HLSL_CLASS_ERROR)
        return arg;

    return add_expr(ctx, block, op, args, arg->data_type, loc);
}

static struct hlsl_ir_node *add_unary_bitwise_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg, const struct vkd3d_shader_location *loc)
{
    if (arg->data_type->class == HLSL_CLASS_ERROR)
        return arg;

    check_integer_type(ctx, arg);

    return add_unary_arithmetic_expr(ctx, block, op, arg, loc);
}

static struct hlsl_ir_node *add_unary_logical_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *bool_type;

    if (arg->data_type->class == HLSL_CLASS_ERROR)
        return arg;

    bool_type = hlsl_get_numeric_type(ctx, arg->data_type->class, HLSL_TYPE_BOOL,
            arg->data_type->e.numeric.dimx, arg->data_type->e.numeric.dimy);
    args[0] = add_implicit_conversion(ctx, block, arg, bool_type, loc);
    return add_expr(ctx, block, op, args, bool_type, loc);
}

static struct hlsl_type *get_common_numeric_type(struct hlsl_ctx *ctx, const struct hlsl_ir_node *arg1,
        const struct hlsl_ir_node *arg2, const struct vkd3d_shader_location *loc)
{
    enum hlsl_type_class type;
    enum hlsl_base_type base;
    unsigned int dimx, dimy;

    if (!expr_common_shape(ctx, arg1->data_type, arg2->data_type, loc, &type, &dimx, &dimy))
        return NULL;
    base = expr_common_base_type(arg1->data_type->e.numeric.type, arg2->data_type->e.numeric.type);
    return hlsl_get_numeric_type(ctx, type, base, dimx, dimy);
}

static struct hlsl_ir_node *add_binary_arithmetic_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg1, struct hlsl_ir_node *arg2,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *common_type;

    if (!(common_type = get_common_numeric_type(ctx, arg1, arg2, loc)))
    {
        block->value = ctx->error_instr;
        return block->value;
    }

    args[0] = add_implicit_conversion(ctx, block, arg1, common_type, loc);
    args[1] = add_implicit_conversion(ctx, block, arg2, common_type, loc);
    return add_expr(ctx, block, op, args, common_type, loc);
}

static struct hlsl_ir_node *add_binary_bitwise_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg1, struct hlsl_ir_node *arg2,
        const struct vkd3d_shader_location *loc)
{
    check_integer_type(ctx, arg1);
    check_integer_type(ctx, arg2);

    return add_binary_arithmetic_expr(ctx, block, op, arg1, arg2, loc);
}

static struct hlsl_ir_node *add_binary_comparison_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg1, struct hlsl_ir_node *arg2,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *common_type, *return_type;
    enum hlsl_type_class type;
    enum hlsl_base_type base;
    unsigned int dimx, dimy;
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};

    if (!expr_common_shape(ctx, arg1->data_type, arg2->data_type, loc, &type, &dimx, &dimy))
        return NULL;

    base = expr_common_base_type(arg1->data_type->e.numeric.type, arg2->data_type->e.numeric.type);
    common_type = hlsl_get_numeric_type(ctx, type, base, dimx, dimy);
    return_type = hlsl_get_numeric_type(ctx, type, HLSL_TYPE_BOOL, dimx, dimy);

    args[0] = add_implicit_conversion(ctx, block, arg1, common_type, loc);
    args[1] = add_implicit_conversion(ctx, block, arg2, common_type, loc);
    return add_expr(ctx, block, op, args, return_type, loc);
}

static struct hlsl_ir_node *add_binary_logical_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg1, struct hlsl_ir_node *arg2,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *common_type;
    enum hlsl_type_class type;
    unsigned int dimx, dimy;

    if (!expr_common_shape(ctx, arg1->data_type, arg2->data_type, loc, &type, &dimx, &dimy))
        return NULL;

    common_type = hlsl_get_numeric_type(ctx, type, HLSL_TYPE_BOOL, dimx, dimy);

    args[0] = add_implicit_conversion(ctx, block, arg1, common_type, loc);
    args[1] = add_implicit_conversion(ctx, block, arg2, common_type, loc);
    return add_expr(ctx, block, op, args, common_type, loc);
}

static struct hlsl_ir_node *add_binary_shift_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        enum hlsl_ir_expr_op op, struct hlsl_ir_node *arg1, struct hlsl_ir_node *arg2,
        const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type base = arg1->data_type->e.numeric.type;
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *return_type, *integer_type;
    enum hlsl_type_class type;
    unsigned int dimx, dimy;

    check_integer_type(ctx, arg1);
    check_integer_type(ctx, arg2);

    if (base == HLSL_TYPE_BOOL)
        base = HLSL_TYPE_INT;

    if (!expr_common_shape(ctx, arg1->data_type, arg2->data_type, loc, &type, &dimx, &dimy))
        return NULL;

    return_type = hlsl_get_numeric_type(ctx, type, base, dimx, dimy);
    integer_type = hlsl_get_numeric_type(ctx, type, HLSL_TYPE_INT, dimx, dimy);

    args[0] = add_implicit_conversion(ctx, block, arg1, return_type, loc);
    args[1] = add_implicit_conversion(ctx, block, arg2, integer_type, loc);
    return add_expr(ctx, block, op, args, return_type, loc);
}

static struct hlsl_ir_node *add_binary_dot_expr(struct hlsl_ctx *ctx, struct hlsl_block *instrs,
        struct hlsl_ir_node *arg1, struct hlsl_ir_node *arg2, const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type base = expr_common_base_type(arg1->data_type->e.numeric.type, arg2->data_type->e.numeric.type);
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *common_type, *ret_type;
    enum hlsl_ir_expr_op op;
    unsigned dim;

    if (arg1->data_type->class == HLSL_CLASS_MATRIX)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, arg1->data_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Invalid type %s.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return NULL;
    }

    if (arg2->data_type->class == HLSL_CLASS_MATRIX)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, arg2->data_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Invalid type %s.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return NULL;
    }

    if (arg1->data_type->class == HLSL_CLASS_SCALAR)
        dim = arg2->data_type->e.numeric.dimx;
    else if (arg2->data_type->class == HLSL_CLASS_SCALAR)
        dim = arg1->data_type->e.numeric.dimx;
    else
        dim = min(arg1->data_type->e.numeric.dimx, arg2->data_type->e.numeric.dimx);

    if (dim == 1)
        op = HLSL_OP2_MUL;
    else
        op = HLSL_OP2_DOT;

    common_type = hlsl_get_vector_type(ctx, base, dim);
    ret_type = hlsl_get_scalar_type(ctx, base);

    args[0] = add_implicit_conversion(ctx, instrs, arg1, common_type, loc);
    args[1] = add_implicit_conversion(ctx, instrs, arg2, common_type, loc);
    return add_expr(ctx, instrs, op, args, ret_type, loc);
}

static struct hlsl_ir_node *add_binary_expr(struct hlsl_ctx *ctx, struct hlsl_block *block, enum hlsl_ir_expr_op op,
        struct hlsl_ir_node *lhs, struct hlsl_ir_node *rhs, const struct vkd3d_shader_location *loc)
{
    switch (op)
    {
        case HLSL_OP2_ADD:
        case HLSL_OP2_DIV:
        case HLSL_OP2_MOD:
        case HLSL_OP2_MUL:
            return add_binary_arithmetic_expr(ctx, block, op, lhs, rhs, loc);

        case HLSL_OP2_BIT_AND:
        case HLSL_OP2_BIT_OR:
        case HLSL_OP2_BIT_XOR:
            return add_binary_bitwise_expr(ctx, block, op, lhs, rhs, loc);

        case HLSL_OP2_LESS:
        case HLSL_OP2_GEQUAL:
        case HLSL_OP2_EQUAL:
        case HLSL_OP2_NEQUAL:
            return add_binary_comparison_expr(ctx, block, op, lhs, rhs, loc);

        case HLSL_OP2_LOGIC_AND:
        case HLSL_OP2_LOGIC_OR:
            return add_binary_logical_expr(ctx, block, op, lhs, rhs, loc);

        case HLSL_OP2_LSHIFT:
        case HLSL_OP2_RSHIFT:
            return add_binary_shift_expr(ctx, block, op, lhs, rhs, loc);

        default:
            vkd3d_unreachable();
    }
}

static struct hlsl_block *add_binary_expr_merge(struct hlsl_ctx *ctx, struct hlsl_block *block1,
        struct hlsl_block *block2, enum hlsl_ir_expr_op op, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg1 = node_from_block(block1), *arg2 = node_from_block(block2);

    hlsl_block_add_block(block1, block2);
    destroy_block(block2);

    if (arg1->data_type->class == HLSL_CLASS_ERROR || arg2->data_type->class == HLSL_CLASS_ERROR)
    {
        block1->value = ctx->error_instr;
        return block1;
    }

    if (add_binary_expr(ctx, block1, op, arg1, arg2, loc) == NULL)
        return NULL;

    return block1;
}

static enum hlsl_ir_expr_op op_from_assignment(enum parse_assign_op op)
{
    static const enum hlsl_ir_expr_op ops[] =
    {
        0,
        HLSL_OP2_ADD,
        0,
        HLSL_OP2_MUL,
        HLSL_OP2_DIV,
        HLSL_OP2_MOD,
        HLSL_OP2_LSHIFT,
        HLSL_OP2_RSHIFT,
        HLSL_OP2_BIT_AND,
        HLSL_OP2_BIT_OR,
        HLSL_OP2_BIT_XOR,
    };

    return ops[op];
}

static bool invert_swizzle(uint32_t *swizzle, unsigned int *writemask, unsigned int *ret_width)
{
    unsigned int i, j, bit = 0, inverted = 0, width, new_writemask = 0, new_swizzle = 0;

    /* Apply the writemask to the swizzle to get a new writemask and swizzle. */
    for (i = 0; i < 4; ++i)
    {
        if (*writemask & (1 << i))
        {
            unsigned int s = hlsl_swizzle_get_component(*swizzle, i);
            hlsl_swizzle_set_component(&new_swizzle, bit++, s);
            if (new_writemask & (1 << s))
                return false;
            new_writemask |= 1 << s;
        }
    }
    width = bit;

    /* Invert the swizzle. */
    bit = 0;
    for (i = 0; i < 4; ++i)
    {
        for (j = 0; j < width; ++j)
        {
            unsigned int s = hlsl_swizzle_get_component(new_swizzle, j);
            if (s == i)
                hlsl_swizzle_set_component(&inverted, bit++, j);
        }
    }

    *swizzle = inverted;
    *writemask = new_writemask;
    *ret_width = width;
    return true;
}

static bool invert_swizzle_matrix(const struct hlsl_matrix_swizzle *swizzle,
        uint32_t *ret_inverted, unsigned int *writemask, unsigned int *ret_width)
{
    unsigned int i, j, bit = 0, inverted = 0, width, new_writemask = 0;
    struct hlsl_matrix_swizzle new_swizzle = {0};

    /* First, we filter the swizzle to remove components that aren't enabled by writemask. */
    for (i = 0; i < 4; ++i)
    {
        if (*writemask & (1 << i))
        {
            unsigned int x = swizzle->components[i].x;
            unsigned int y = swizzle->components[i].y;
            unsigned int idx = x + y * 4;

            new_swizzle.components[bit++] = swizzle->components[i];
            if (new_writemask & (1 << idx))
                return false;
            new_writemask |= 1 << idx;
        }
    }
    width = bit;

    /* Then we invert the swizzle. The resulting swizzle uses a uint32_t
     * vector format, because it's for the incoming vector. */
    bit = 0;
    for (i = 0; i < 16; ++i)
    {
        for (j = 0; j < width; ++j)
        {
            unsigned int x = new_swizzle.components[j].x;
            unsigned int y = new_swizzle.components[j].y;
            unsigned int idx = x + y * 4;
            if (idx == i)
                hlsl_swizzle_set_component(&inverted, bit++, j);
        }
    }

    *ret_inverted = inverted;
    *writemask = new_writemask;
    *ret_width = width;
    return true;
}

static bool add_assignment(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *lhs,
        enum parse_assign_op assign_op, struct hlsl_ir_node *rhs, bool is_function_out_arg)
{
    struct hlsl_type *lhs_type = lhs->data_type;
    unsigned int writemask = 0, width = 0;
    bool matrix_writemask = false;
    bool first_cast = true;

    if (lhs->data_type->class == HLSL_CLASS_ERROR || rhs->data_type->class == HLSL_CLASS_ERROR)
    {
        block->value = ctx->error_instr;
        return true;
    }

    if (assign_op == ASSIGN_OP_SUB)
    {
        if (!(rhs = add_unary_arithmetic_expr(ctx, block, HLSL_OP1_NEG, rhs, &rhs->loc)))
            return false;
        assign_op = ASSIGN_OP_ADD;
    }
    if (assign_op != ASSIGN_OP_ASSIGN)
    {
        enum hlsl_ir_expr_op op = op_from_assignment(assign_op);

        VKD3D_ASSERT(op);
        if (!(rhs = add_binary_expr(ctx, block, op, lhs, rhs, &rhs->loc)))
            return false;
    }

    if (hlsl_is_numeric_type(lhs_type))
    {
        unsigned int size = hlsl_type_component_count(lhs_type);

        writemask = (1 << size) - 1;
        width = size;
    }

    rhs = add_implicit_conversion(ctx, block, rhs, lhs_type, &rhs->loc);

    while (lhs->type != HLSL_IR_LOAD && lhs->type != HLSL_IR_INDEX)
    {
        if (lhs->type == HLSL_IR_EXPR && hlsl_ir_expr(lhs)->op == HLSL_OP1_CAST)
        {
            struct hlsl_ir_node *cast = lhs;
            lhs = hlsl_ir_expr(cast)->operands[0].node;

            if (hlsl_type_component_count(lhs->data_type) != hlsl_type_component_count(cast->data_type))
            {
                hlsl_fixme(ctx, &cast->loc, "Size change on the LHS.");
                return false;
            }
            if (hlsl_version_ge(ctx, 4, 0) && (!is_function_out_arg || !first_cast))
            {
                hlsl_error(ctx, &cast->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_LVALUE,
                        "Base type casts are not allowed on the LHS for profiles >= 4.");
                return false;
            }

            lhs_type = lhs->data_type;
            if (lhs_type->class == HLSL_CLASS_VECTOR || (lhs_type->class == HLSL_CLASS_MATRIX && matrix_writemask))
                lhs_type = hlsl_get_vector_type(ctx, lhs->data_type->e.numeric.type, width);

            first_cast = false;
        }
        else if (lhs->type == HLSL_IR_SWIZZLE)
        {
            struct hlsl_ir_swizzle *swizzle = hlsl_ir_swizzle(lhs);
            uint32_t s;

            VKD3D_ASSERT(!matrix_writemask);

            if (swizzle->val.node->data_type->class == HLSL_CLASS_MATRIX)
            {
                struct hlsl_matrix_swizzle ms = swizzle->u.matrix;

                if (swizzle->val.node->type != HLSL_IR_LOAD && swizzle->val.node->type != HLSL_IR_INDEX)
                {
                    hlsl_fixme(ctx, &lhs->loc, "Unhandled source of matrix swizzle.");
                    return false;
                }
                if (!invert_swizzle_matrix(&ms, &s, &writemask, &width))
                {
                    hlsl_error(ctx, &lhs->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_WRITEMASK, "Invalid writemask for matrix.");
                    return false;
                }
                matrix_writemask = true;
            }
            else
            {
                s = swizzle->u.vector;
                if (!invert_swizzle(&s, &writemask, &width))
                {
                    hlsl_error(ctx, &lhs->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_WRITEMASK, "Invalid writemask.");
                    return false;
                }
            }

            rhs = hlsl_block_add_swizzle(ctx, block, s, width, rhs, &swizzle->node.loc);
            lhs = swizzle->val.node;
            lhs_type = hlsl_get_vector_type(ctx, lhs_type->e.numeric.type, width);
        }
        else
        {
            hlsl_error(ctx, &lhs->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_LVALUE, "Invalid lvalue.");
            return false;
        }
    }

    /* lhs casts could have resulted in a discrepancy between the
     * rhs->data_type and the type of the variable that will be ulimately
     * stored to. This is corrected. */
    rhs = add_cast(ctx, block, rhs, lhs_type, &rhs->loc);

    if (lhs->type == HLSL_IR_INDEX && hlsl_index_chain_has_resource_access(hlsl_ir_index(lhs)))
    {
        struct hlsl_ir_node *coords = hlsl_ir_index(lhs)->idx.node;
        struct hlsl_deref resource_deref;
        struct hlsl_type *resource_type;
        unsigned int dim_count;

        if (!hlsl_index_is_resource_access(hlsl_ir_index(lhs)))
        {
            hlsl_fixme(ctx, &lhs->loc, "Non-direct structured resource store.");
            return false;
        }

        if (!hlsl_init_deref_from_index_chain(ctx, &resource_deref, hlsl_ir_index(lhs)->val.node))
            return false;

        resource_type = hlsl_deref_get_type(ctx, &resource_deref);
        VKD3D_ASSERT(resource_type->class == HLSL_CLASS_TEXTURE || resource_type->class == HLSL_CLASS_UAV);

        if (resource_type->class != HLSL_CLASS_UAV)
            hlsl_error(ctx, &lhs->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Read-only resources cannot be stored to.");

        dim_count = hlsl_sampler_dim_count(resource_type->sampler_dim);

        if (width != resource_type->e.resource.format->e.numeric.dimx * resource_type->e.resource.format->e.numeric.dimy)
            hlsl_error(ctx, &lhs->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_WRITEMASK,
                    "Resource store expressions must write to all components.");

        VKD3D_ASSERT(coords->data_type->class == HLSL_CLASS_VECTOR);
        VKD3D_ASSERT(coords->data_type->e.numeric.type == HLSL_TYPE_UINT);
        VKD3D_ASSERT(coords->data_type->e.numeric.dimx == dim_count);

        hlsl_block_add_resource_store(ctx, block, HLSL_RESOURCE_STORE,
                &resource_deref, coords, rhs, writemask, &lhs->loc);
        hlsl_cleanup_deref(&resource_deref);
    }
    else if (matrix_writemask)
    {
        struct hlsl_deref deref;
        unsigned int i, j, k = 0;

        hlsl_init_deref_from_index_chain(ctx, &deref, lhs);

        for (i = 0; i < lhs->data_type->e.numeric.dimy; ++i)
        {
            for (j = 0; j < lhs->data_type->e.numeric.dimx; ++j)
            {
                struct hlsl_ir_node *load;
                const unsigned int idx = i * 4 + j;
                const unsigned int component = i * lhs->data_type->e.numeric.dimx + j;

                if (!(writemask & (1 << idx)))
                    continue;

                load = hlsl_add_load_component(ctx, block, rhs, k++, &rhs->loc);
                hlsl_block_add_store_component(ctx, block, &deref, component, load);
            }
        }

        hlsl_cleanup_deref(&deref);
    }
    else if (lhs->type == HLSL_IR_INDEX && hlsl_index_is_noncontiguous(hlsl_ir_index(lhs)))
    {
        struct hlsl_ir_index *row = hlsl_ir_index(lhs);
        struct hlsl_ir_node *mat = row->val.node;
        unsigned int i, k = 0;

        VKD3D_ASSERT(!matrix_writemask);

        for (i = 0; i < mat->data_type->e.numeric.dimx; ++i)
        {
            struct hlsl_ir_node *cell, *load, *c;
            struct hlsl_deref deref;

            if (!(writemask & (1 << i)))
                continue;

            c = hlsl_block_add_uint_constant(ctx, block, i, &lhs->loc);

            cell = hlsl_block_add_index(ctx, block, &row->node, c, &lhs->loc);
            load = hlsl_add_load_component(ctx, block, rhs, k++, &rhs->loc);

            if (!hlsl_init_deref_from_index_chain(ctx, &deref, cell))
                return false;

            hlsl_block_add_store_index(ctx, block, &deref, NULL, load, 0, &rhs->loc);
            hlsl_cleanup_deref(&deref);
        }
    }
    else
    {
        struct hlsl_deref deref;

        if (!hlsl_init_deref_from_index_chain(ctx, &deref, lhs))
            return false;

        hlsl_block_add_store_index(ctx, block, &deref, NULL, rhs, writemask, &rhs->loc);
        hlsl_cleanup_deref(&deref);
    }

    block->value = rhs;
    return true;
}

static bool add_increment(struct hlsl_ctx *ctx, struct hlsl_block *block, bool decrement, bool post,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *lhs = node_from_block(block);
    struct hlsl_ir_node *one;

    if (lhs->data_type->class == HLSL_CLASS_ERROR)
        return true;

    if (lhs->data_type->modifiers & HLSL_MODIFIER_CONST)
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_MODIFIES_CONST,
                "Argument to %s%screment operator is const.", post ? "post" : "pre", decrement ? "de" : "in");

    one = hlsl_block_add_int_constant(ctx, block, 1, loc);

    if (!add_assignment(ctx, block, lhs, decrement ? ASSIGN_OP_SUB : ASSIGN_OP_ADD, one, false))
        return false;

    if (post)
    {
        struct hlsl_ir_node *copy;

        if (!(copy = hlsl_new_copy(ctx, lhs)))
            return false;
        hlsl_block_add_instr(block, copy);

        /* Post increment/decrement expressions are considered const. */
        if (!(copy->data_type = hlsl_type_clone(ctx, copy->data_type, 0, HLSL_MODIFIER_CONST)))
            return false;
    }

    return true;
}

static void initialize_var_components(struct hlsl_ctx *ctx, struct hlsl_block *instrs,
        struct hlsl_ir_var *dst, unsigned int *store_index, struct hlsl_ir_node *src,
        bool is_default_values_initializer)
{
    unsigned int src_comp_count = hlsl_type_component_count(src->data_type);
    struct hlsl_deref dst_deref;
    unsigned int k;

    hlsl_init_simple_deref_from_var(&dst_deref, dst);

    for (k = 0; k < src_comp_count; ++k)
    {
        struct hlsl_ir_node *conv, *load;
        struct hlsl_type *dst_comp_type;
        struct hlsl_block block;

        load = hlsl_add_load_component(ctx, instrs, src, k, &src->loc);

        dst_comp_type = hlsl_type_get_component_type(ctx, dst->data_type, *store_index);

        if (is_default_values_initializer)
        {
            struct hlsl_default_value default_value = {0};

            if ((src->type == HLSL_IR_SAMPLER_STATE || src->type == HLSL_IR_COMPILE)
                    && hlsl_is_numeric_type(dst_comp_type) && dst->default_values)
            {
                /* Default values are discarded if they contain an object
                 * literal expression for a numeric component. */
                hlsl_warning(ctx, &src->loc, VKD3D_SHADER_WARNING_HLSL_IGNORED_DEFAULT_VALUE,
                        "Component %u in variable '%s' initializer is an object literal. Default values discarded.",
                        k, dst->name);
                hlsl_free_default_values(dst);
            }
            else if (src->type != HLSL_IR_SAMPLER_STATE)
            {
                if (!hlsl_clone_block(ctx, &block, instrs))
                    return;
                default_value = evaluate_static_expression(ctx, &block, dst_comp_type, &src->loc);

                if (dst->default_values)
                    dst->default_values[*store_index] = default_value;
                else
                    hlsl_free_default_value(&default_value);

                hlsl_block_cleanup(&block);
            }
        }
        else
        {
            if (src->type == HLSL_IR_SAMPLER_STATE)
            {
                /* Sampler states end up in the variable's state_blocks instead of
                 * being used to initialize its value. */
                struct hlsl_ir_sampler_state *sampler_state = hlsl_ir_sampler_state(src);

                if (dst_comp_type->class != HLSL_CLASS_SAMPLER)
                {
                    struct vkd3d_string_buffer *dst_string;

                    dst_string = hlsl_type_to_string(ctx, dst_comp_type);
                    if (dst_string)
                        hlsl_error(ctx, &src->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                                "Cannot assign sampler_state to %s.", dst_string->buffer);
                    hlsl_release_string_buffer(ctx, dst_string);
                    return;
                }

                if (!hlsl_array_reserve(ctx, (void **)&dst->state_blocks, &dst->state_block_capacity,
                        dst->state_block_count + 1, sizeof(*dst->state_blocks)))
                    return;

                dst->state_blocks[dst->state_block_count] = sampler_state->state_block;
                sampler_state->state_block = NULL;
                ++dst->state_block_count;
            }
            else
            {
                conv = add_implicit_conversion(ctx, instrs, load, dst_comp_type, &src->loc);
                hlsl_block_add_store_component(ctx, instrs, &dst_deref, *store_index, conv);
            }
        }

        ++*store_index;
    }
}

static void initialize_var(struct hlsl_ctx *ctx, struct hlsl_ir_var *dst,
        const struct parse_initializer *initializer, bool is_default_values_initializer)
{
    unsigned int store_index = 0;

    /* If any of the elements has an error type, then initializer_size() is not
     * meaningful. */
    for (unsigned int i = 0; i < initializer->args_count; ++i)
    {
        if (initializer->args[i]->data_type->class == HLSL_CLASS_ERROR)
            return;
    }

    if (initializer_size(initializer) != hlsl_type_component_count(dst->data_type))
    {
        hlsl_error(ctx, &initializer->loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Expected %u components in initializer, but got %u.",
                hlsl_type_component_count(dst->data_type), initializer_size(initializer));
        return;
    }

    for (unsigned int i = 0; i < initializer->args_count; ++i)
        initialize_var_components(ctx, initializer->instrs, dst, &store_index,
                initializer->args[i], is_default_values_initializer);
}

static bool type_has_object_components(const struct hlsl_type *type)
{
    if (type->class == HLSL_CLASS_ARRAY)
        return type_has_object_components(type->e.array.type);

    if (type->class == HLSL_CLASS_STRUCT)
    {
        for (unsigned int i = 0; i < type->e.record.field_count; ++i)
        {
            if (type_has_object_components(type->e.record.fields[i].type))
                return true;
        }

        return false;
    }

    return !hlsl_is_numeric_type(type);
}

static bool type_has_numeric_components(struct hlsl_type *type)
{
    if (hlsl_is_numeric_type(type))
        return true;
    if (type->class == HLSL_CLASS_ARRAY)
        return type_has_numeric_components(type->e.array.type);

    if (type->class == HLSL_CLASS_STRUCT)
    {
        unsigned int i;

        for (i = 0; i < type->e.record.field_count; ++i)
        {
            if (type_has_numeric_components(type->e.record.fields[i].type))
                return true;
        }
    }
    return false;
}

static void check_invalid_non_parameter_modifiers(struct hlsl_ctx *ctx, unsigned int modifiers,
        const struct vkd3d_shader_location *loc)
{
    modifiers &= (HLSL_STORAGE_IN | HLSL_STORAGE_OUT | HLSL_PRIMITIVE_MODIFIERS_MASK);
    if (modifiers)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_modifiers_to_string(ctx, modifiers)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                    "Modifiers '%s' are not allowed on non-parameter variables.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }
}

static void check_invalid_object_fields(struct hlsl_ctx *ctx, const struct hlsl_ir_var *var)
{
    const struct hlsl_type *type = var->data_type;

    while (type->class == HLSL_CLASS_ARRAY)
        type = type->e.array.type;

    if (type->class == HLSL_CLASS_STRUCT && type_has_object_components(type))
        hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Target profile doesn't support objects as struct members in uniform variables.");
}

static void validate_groupshared_var(struct hlsl_ctx *ctx, const struct hlsl_ir_var *var)
{
    if (type_has_object_components(var->data_type))
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, var->data_type)))
        {
            hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Groupshared type %s is not numeric.", string->buffer);
            hlsl_release_string_buffer(ctx, string);
        }
    }
}

static void declare_var(struct hlsl_ctx *ctx, struct parse_variable_def *v)
{
    uint32_t modifiers = v->modifiers | v->semantic.modifiers;
    struct hlsl_type *basic_type = v->basic_type;
    struct hlsl_ir_function_decl *func;
    struct hlsl_semantic new_semantic;
    bool unbounded_res_array = false;
    bool constant_buffer = false;
    struct hlsl_ir_var *var;
    struct hlsl_type *type;
    bool stream_output;
    char *var_name;
    unsigned int i;

    VKD3D_ASSERT(basic_type);

    if (basic_type->class == HLSL_CLASS_MATRIX)
        VKD3D_ASSERT(basic_type->modifiers & HLSL_MODIFIERS_MAJORITY_MASK);

    type = basic_type;

    if (hlsl_version_ge(ctx, 5, 1) && hlsl_type_is_resource(type))
    {
        for (i = 0; i < v->arrays.count; ++i)
            unbounded_res_array |= (v->arrays.sizes[i] == HLSL_ARRAY_ELEMENTS_COUNT_IMPLICIT);
    }

    if (type->class == HLSL_CLASS_CONSTANT_BUFFER)
    {
        type = type->e.resource.format;
        constant_buffer = true;
    }

    if (unbounded_res_array)
    {
        if (v->arrays.count == 1)
        {
            hlsl_fixme(ctx, &v->loc, "Unbounded resource arrays.");
            return;
        }
        else
        {
            hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Unbounded resource arrays cannot be multi-dimensional.");
        }
    }
    else
    {
        for (i = 0; i < v->arrays.count; ++i)
        {
            if (v->arrays.sizes[i] == HLSL_ARRAY_ELEMENTS_COUNT_IMPLICIT)
            {
                unsigned int size = initializer_size(&v->initializer);
                unsigned int elem_components = hlsl_type_component_count(type);

                if (i < v->arrays.count - 1)
                {
                    hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Only innermost array size can be implicit.");
                    type = ctx->builtin_types.error;
                    break;
                }
                else if (elem_components == 0)
                {
                    hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Cannot declare an implicit size array of a size 0 type.");
                    type = ctx->builtin_types.error;
                    break;
                }
                else if (size == 0)
                {
                    hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Implicit size arrays need to be initialized.");
                    type = ctx->builtin_types.error;
                    break;
                }
                else if (size % elem_components != 0)
                {
                    hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                            "Cannot initialize implicit size array with %u components, expected a multiple of %u.",
                            size, elem_components);
                    type = ctx->builtin_types.error;
                    break;
                }
                else
                {
                    v->arrays.sizes[i] = size / elem_components;
                }
            }
            type = hlsl_new_array_type(ctx, type, v->arrays.sizes[i], HLSL_ARRAY_GENERIC);
        }
    }

    if (hlsl_version_ge(ctx, 5, 1) && type->class == HLSL_CLASS_ARRAY && hlsl_type_is_resource(type))
    {
        /* SM 5.1/6.x descriptor arrays act differently from previous versions.
         * Not only are they treated as a single object in reflection, but they
         * act as a single component for the purposes of assignment and
         * initialization. */
        hlsl_fixme(ctx, &v->loc, "Shader model 5.1+ resource array.");
    }

    stream_output = !!hlsl_get_stream_output_type(type);
    if (stream_output)
        check_invalid_stream_output_object(ctx, type, v->name, &v->loc);

    if (!(var_name = vkd3d_strdup(v->name)))
        return;

    if (!hlsl_clone_semantic(ctx, &new_semantic, &v->semantic))
    {
        vkd3d_free(var_name);
        return;
    }

    if (!(var = hlsl_new_var(ctx, var_name, type, &v->loc, &new_semantic, modifiers, &v->reg_reservation)))
    {
        hlsl_cleanup_semantic(&new_semantic);
        vkd3d_free(var_name);
        return;
    }

    var->annotations = v->annotations;

    if (constant_buffer && ctx->cur_scope == ctx->globals)
    {
        if (!(var_name = vkd3d_strdup(v->name)))
            return;
        var->buffer = hlsl_new_buffer(ctx, HLSL_BUFFER_CONSTANT, var_name, modifiers, &v->reg_reservation, NULL, &v->loc);
    }
    else
    {
        var->buffer = ctx->cur_buffer;
    }

    if (var->buffer == ctx->globals_buffer)
    {
        if (var->reg_reservation.offset_type)
            hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                    "packoffset() is only allowed inside constant buffer declarations.");
    }
    else
    {
        if ((type->modifiers & HLSL_MODIFIER_CONST) && !v->initializer.args_count)
            hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_MISSING_INITIALIZER,
                    "Const variable \"%s\" is missing an initializer.", var->name);
    }

    if (ctx->cur_scope == ctx->globals)
    {
        if ((modifiers & HLSL_STORAGE_UNIFORM) && (modifiers & HLSL_STORAGE_STATIC))
            hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                    "Variable '%s' is declared as both \"uniform\" and \"static\".", var->name);

        if ((modifiers & HLSL_STORAGE_GROUPSHARED))
        {
            /* d3dcompiler/fxc always validates global groupshared variables,
             * regardless of whether the groupshared modifier is ignored. */
            validate_groupshared_var(ctx, var);

            if (ctx->profile->type != VKD3D_SHADER_TYPE_COMPUTE)
            {
                modifiers &= ~HLSL_STORAGE_GROUPSHARED;
                hlsl_warning(ctx, &var->loc, VKD3D_SHADER_WARNING_HLSL_IGNORED_MODIFIER,
                        "Ignoring the 'groupshared' modifier in a non-compute shader.");
            }
        }

        /* Mark it as uniform. We need to do this here since synthetic
            * variables also get put in the global scope, but shouldn't be
            * considered uniforms, and we have no way of telling otherwise. */
        if (!(modifiers & (HLSL_STORAGE_STATIC | HLSL_STORAGE_GROUPSHARED)))
            var->storage_modifiers |= HLSL_STORAGE_UNIFORM;

        if (stream_output)
            hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_MISPLACED_STREAM_OUTPUT,
                    "Stream output object '%s' is not allowed in the global scope.", var->name);

        if ((ctx->profile->major_version < 5 || ctx->profile->type == VKD3D_SHADER_TYPE_EFFECT)
                && (var->storage_modifiers & HLSL_STORAGE_UNIFORM))
        {
            check_invalid_object_fields(ctx, var);
        }

        if ((func = hlsl_get_first_func_decl(ctx, var->name)))
        {
            hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_REDEFINED,
                    "'%s' is already defined as a function.", var->name);
            hlsl_note(ctx, &func->loc, VKD3D_SHADER_LOG_ERROR,
                    "'%s' was previously defined here.", var->name);
        }
    }
    else
    {
        static const unsigned int invalid = HLSL_STORAGE_EXTERN | HLSL_STORAGE_SHARED
                | HLSL_STORAGE_GROUPSHARED | HLSL_STORAGE_UNIFORM;

        if (modifiers & invalid)
        {
            struct vkd3d_string_buffer *string;

            if ((string = hlsl_modifiers_to_string(ctx, modifiers & invalid)))
                hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                        "Modifiers '%s' are not allowed on local variables.", string->buffer);
            hlsl_release_string_buffer(ctx, string);
        }
        if (var->semantic.name)
            hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SEMANTIC,
                    "Semantics are not allowed on local variables.");

        if ((type->modifiers & HLSL_MODIFIER_CONST) && !v->initializer.args_count && !(modifiers & HLSL_STORAGE_STATIC))
        {
            hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_MISSING_INITIALIZER,
                "Const variable \"%s\" is missing an initializer.", var->name);
        }

        if (var->annotations)
        {
            hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                    "Annotations are only allowed for objects in the global scope.");
        }
    }

    if ((var->storage_modifiers & HLSL_STORAGE_STATIC) && type_has_numeric_components(var->data_type)
            && type_has_object_components(var->data_type))
    {
        hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Static variables cannot have both numeric and resource components.");
    }

    hlsl_add_var(ctx, var);
}

static struct hlsl_block *initialize_vars(struct hlsl_ctx *ctx, struct list *var_list)
{
    struct parse_variable_def *v, *v_next;
    struct hlsl_block *initializers;
    unsigned int component_count;
    struct hlsl_ir_var *var;
    struct hlsl_type *type;

    if (!(initializers = make_empty_block(ctx)))
    {
        destroy_parse_variable_defs(var_list);
        return NULL;
    }

    LIST_FOR_EACH_ENTRY_SAFE(v, v_next, var_list, struct parse_variable_def, entry)
    {
        /* If this fails, the variable failed to be declared. */
        if (!(var = hlsl_get_var(ctx->cur_scope, v->name)))
        {
            free_parse_variable_def(v);
            continue;
        }

        type = var->data_type;
        component_count = hlsl_type_component_count(type);

        var->state_blocks = v->state_blocks;
        var->state_block_count = v->state_block_count;
        var->state_block_capacity = v->state_block_capacity;
        v->state_block_count = 0;
        v->state_block_capacity = 0;
        v->state_blocks = NULL;

        if (var->state_blocks && component_count != var->state_block_count)
        {
            hlsl_error(ctx, &v->loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                    "Expected %u state blocks, but got %u.", component_count, var->state_block_count);
            free_parse_variable_def(v);
            continue;
        }

        if (v->initializer.args_count)
        {
            bool is_default_values_initializer, static_initialization;

            is_default_values_initializer = (ctx->cur_buffer != ctx->globals_buffer)
                    || (var->storage_modifiers & HLSL_STORAGE_UNIFORM)
                    || ctx->cur_scope->annotations;
            if (hlsl_get_multiarray_element_type(type)->class == HLSL_CLASS_SAMPLER)
                is_default_values_initializer = false;

            static_initialization = var->storage_modifiers & HLSL_STORAGE_STATIC
                    || (var->data_type->modifiers & HLSL_MODIFIER_CONST
                    && is_static_expression(v->initializer.instrs));

            if (is_default_values_initializer)
            {
                /* Default values might have been allocated already for another variable of the same name,
                   in the same scope. */
                if (var->default_values)
                {
                    free_parse_variable_def(v);
                    continue;
                }

                if (!(var->default_values = hlsl_calloc(ctx, component_count, sizeof(*var->default_values))))
                {
                    free_parse_variable_def(v);
                    continue;
                }
            }

            if (!v->initializer.braces)
                v->initializer.args[0] = add_implicit_conversion(ctx,
                        v->initializer.instrs, v->initializer.args[0], type, &v->loc);

            if (var->data_type->class != HLSL_CLASS_ERROR)
                initialize_var(ctx, var, &v->initializer, is_default_values_initializer);

            if (is_default_values_initializer)
            {
                hlsl_dump_var_default_values(var);
            }
            else if (static_initialization)
            {
                hlsl_block_add_block(&ctx->static_initializers, v->initializer.instrs);
            }
            else
            {
                hlsl_block_add_block(initializers, v->initializer.instrs);
            }

            if (var->state_blocks)
                TRACE("Variable %s has %u state blocks.\n", var->name, var->state_block_count);
        }
        else if (var->storage_modifiers & HLSL_STORAGE_STATIC)
        {
            struct hlsl_ir_node *cast, *zero;

            /* Initialize statics to zero by default. */

            if (type_has_object_components(var->data_type))
            {
                free_parse_variable_def(v);
                continue;
            }

            zero = hlsl_block_add_uint_constant(ctx, &ctx->static_initializers, 0, &var->loc);
            cast = add_cast(ctx, &ctx->static_initializers, zero, var->data_type, &var->loc);
            hlsl_block_add_simple_store(ctx, &ctx->static_initializers, var, cast);
        }
        free_parse_variable_def(v);
    }

    vkd3d_free(var_list);
    return initializers;
}

static bool func_is_compatible_match(struct hlsl_ctx *ctx, const struct hlsl_ir_function_decl *decl,
        bool is_compile, const struct parse_initializer *args)
{
    unsigned int i, k;

    k = 0;
    for (i = 0; i < decl->parameters.count; ++i)
    {
        if (is_compile && !(decl->parameters.vars[i]->storage_modifiers & HLSL_STORAGE_UNIFORM))
            continue;

        if (k >= args->args_count)
        {
            if (!decl->parameters.vars[i]->default_values)
                return false;
            return true;
        }

        if (!implicit_compatible_data_types(ctx, args->args[k]->data_type, decl->parameters.vars[i]->data_type))
            return false;

        ++k;
    }
    if (k < args->args_count)
        return false;
    return true;
}

static enum hlsl_base_type hlsl_base_type_class(enum hlsl_base_type t)
{
    switch (t)
    {
        case HLSL_TYPE_HALF:
        case HLSL_TYPE_FLOAT:
        case HLSL_TYPE_DOUBLE:
            return HLSL_TYPE_FLOAT;

        case HLSL_TYPE_INT:
        case HLSL_TYPE_MIN16UINT:
        case HLSL_TYPE_UINT:
            return HLSL_TYPE_INT;

        case HLSL_TYPE_BOOL:
            return HLSL_TYPE_BOOL;
    }

    return 0;
}

static unsigned int hlsl_base_type_width(enum hlsl_base_type t)
{
    switch (t)
    {
        case HLSL_TYPE_HALF:
        case HLSL_TYPE_MIN16UINT:
            return 16;

        case HLSL_TYPE_FLOAT:
        case HLSL_TYPE_INT:
        case HLSL_TYPE_UINT:
        case HLSL_TYPE_BOOL:
            return 32;

        case HLSL_TYPE_DOUBLE:
            return 64;
    }

    return 0;
}

static uint32_t get_argument_conversion_mask(const struct hlsl_ir_var *parameter, const struct hlsl_ir_node *arg)
{
    enum
    {
        COMPONENT_COUNT_WIDENING      = 1u << 0,
        COMPONENT_TYPE_NARROWING      = 1u << 1,
        COMPONENT_TYPE_MISMATCH       = 1u << 2,
        COMPONENT_TYPE_CLASS_MISMATCH = 1u << 3,
        COMPONENT_COUNT_NARROWING     = 1u << 4,
    };
    struct
    {
        enum hlsl_base_type type;
        enum hlsl_base_type class;
        unsigned int count, width;
    } p, a;
    uint32_t mask = 0;

    /* TODO: Non-numeric types. */
    if (!hlsl_is_numeric_type(arg->data_type))
        return 0;

    p.type = parameter->data_type->e.numeric.type;
    p.class = hlsl_base_type_class(p.type);
    p.count = hlsl_type_component_count(parameter->data_type);
    p.width = hlsl_base_type_width(p.type);

    a.type = arg->data_type->e.numeric.type;
    a.class = hlsl_base_type_class(a.type);
    a.count = hlsl_type_component_count(arg->data_type);
    a.width = hlsl_base_type_width(a.type);

    /* Component count narrowing. E.g., passing a float4 argument to a float2
     * or int2 parameter. */
    if (a.count > p.count)
        mask |= COMPONENT_COUNT_NARROWING;
    /* Different component type classes. E.g., passing an int argument to a
     * float parameter. */
    if (a.class != p.class)
        mask |= COMPONENT_TYPE_CLASS_MISMATCH;
    /* Different component types. E.g., passing an int argument to an uint
     * parameter. */
    if (a.type != p.type)
        mask |= COMPONENT_TYPE_MISMATCH;
    /* Component type narrowing. E.g., passing a float argument to a half
     * parameter. */
    if (a.width > p.width)
        mask |= COMPONENT_TYPE_NARROWING;
    /* Component count widening. E.g., passing an int2 argument to an int4
     * parameter. */
    if (a.count < p.count)
        mask |= COMPONENT_COUNT_WIDENING;

    return mask;
}

static int function_compare(const struct hlsl_ir_function_decl *candidate,
        const struct hlsl_ir_function_decl *ref, const struct parse_initializer *args)
{
    uint32_t candidate_mask = 0, ref_mask = 0, c, r;
    bool any_worse = false, any_better = false;
    unsigned int i;
    int ret;

    for (i = 0; i < args->args_count; ++i)
    {
        candidate_mask |= (c = get_argument_conversion_mask(candidate->parameters.vars[i], args->args[i]));
        ref_mask |= (r = get_argument_conversion_mask(ref->parameters.vars[i], args->args[i]));

        if (c > r)
            any_worse = true;
        else if (c < r)
            any_better = true;
    }

    /* We consider a candidate better if at least one parameter is a better
     * match, and none are a worse match. */
    if ((ret = any_better - any_worse))
        return ret;
    /* Otherwise, consider the kind of conversions across all parameters. */
    return vkd3d_u32_compare(ref_mask, candidate_mask);
}

static struct hlsl_ir_function_decl *find_function_call(struct hlsl_ctx *ctx,
        const char *name, const struct parse_initializer *args, bool is_compile,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *decl;
    struct vkd3d_string_buffer *s;
    struct hlsl_ir_function *func;
    struct rb_entry *entry;
    int compare;
    size_t i;
    struct
    {
        struct hlsl_ir_function_decl **candidates;
        size_t count, capacity;
    } candidates = {0};

    if (!(entry = rb_get(&ctx->functions, name)))
        return NULL;
    func = RB_ENTRY_VALUE(entry, struct hlsl_ir_function, entry);

    LIST_FOR_EACH_ENTRY(decl, &func->overloads, struct hlsl_ir_function_decl, entry)
    {
        if (!func_is_compatible_match(ctx, decl, is_compile, args))
            continue;

        if (candidates.count)
        {
            compare = function_compare(decl, candidates.candidates[0], args);

            /* The candidate is worse; skip it. */
            if (compare < 0)
                continue;

            /* The candidate is better; replace the current candidates. */
            if (compare > 0)
            {
                candidates.candidates[0] = decl;
                candidates.count = 1;
                continue;
            }
        }

        if (!(hlsl_array_reserve(ctx, (void **)&candidates.candidates,
                &candidates.capacity, candidates.count + 1, sizeof(decl))))
        {
            vkd3d_free(candidates.candidates);
            return NULL;
        }
        candidates.candidates[candidates.count++] = decl;
    }

    if (!candidates.count)
        return NULL;

    if (candidates.count > 1)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_AMBIGUOUS_CALL, "Ambiguous function call.");
        if ((s = hlsl_get_string_buffer(ctx)))
        {
            hlsl_note(ctx, loc, VKD3D_SHADER_LOG_ERROR, "Candidates are:");
            for (i = 0; i < candidates.count; ++i)
            {
                hlsl_dump_ir_function_decl(ctx, s, candidates.candidates[i]);
                hlsl_note(ctx, loc, VKD3D_SHADER_LOG_ERROR, "    %s;", s->buffer);
                vkd3d_string_buffer_clear(s);
            }
            hlsl_release_string_buffer(ctx, s);
        }
    }

    decl = candidates.candidates[0];
    vkd3d_free(candidates.candidates);

    return decl;
}

static void add_void_expr(struct hlsl_ctx *ctx, struct hlsl_block *block,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};

    hlsl_block_add_expr(ctx, block, HLSL_OP0_VOID, operands, ctx->builtin_types.Void, loc);
}

static bool parse_function_call_arguments(struct hlsl_ctx *ctx, const struct hlsl_ir_function_decl *func,
        const struct parse_initializer *args, bool is_compile, const struct vkd3d_shader_location *loc)
{
    unsigned int i, j, k;

    VKD3D_ASSERT(args->args_count <= func->parameters.count);

    k = 0;
    for (i = 0; i < func->parameters.count; ++i)
    {
        struct hlsl_ir_var *param = func->parameters.vars[i];
        struct hlsl_ir_node *arg;

        if (is_compile && !(param->storage_modifiers & HLSL_STORAGE_UNIFORM))
            continue;

        if (k >= args->args_count)
            break;
        arg = args->args[k];

        if (param->storage_modifiers & HLSL_STORAGE_IN)
        {
            if (!hlsl_types_are_equal(arg->data_type, param->data_type))
                arg = add_cast(ctx, args->instrs, arg, param->data_type, &arg->loc);

            hlsl_block_add_simple_store(ctx, args->instrs, param, arg);
        }

        ++k;
    }

    /* Add default values for the remaining parameters. */
    for (; i < func->parameters.count; ++i)
    {
        struct hlsl_ir_var *param = func->parameters.vars[i];
        unsigned int comp_count = hlsl_type_component_count(param->data_type);
        struct hlsl_deref param_deref;

        VKD3D_ASSERT(param->default_values);

        if (is_compile && !(param->storage_modifiers & HLSL_STORAGE_UNIFORM))
            continue;

        hlsl_init_simple_deref_from_var(&param_deref, param);

        for (j = 0; j < comp_count; ++j)
        {
            struct hlsl_type *type = hlsl_type_get_component_type(ctx, param->data_type, j);
            struct hlsl_constant_value value;
            struct hlsl_ir_node *comp;

            if (!param->default_values[j].string)
            {
                value.u[0] = param->default_values[j].number;
                comp = hlsl_block_add_constant(ctx, args->instrs, type, &value, loc);
                hlsl_block_add_store_component(ctx, args->instrs, &param_deref, j, comp);
            }
        }
    }

    return true;
}

static struct hlsl_ir_node *add_user_call(struct hlsl_ctx *ctx, struct hlsl_ir_function_decl *func,
        const struct parse_initializer *args, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *call;
    unsigned int i;

    if (!parse_function_call_arguments(ctx, func, args, false, loc))
        return NULL;

    if (!(call = hlsl_new_call(ctx, func, loc)))
        return NULL;

    hlsl_block_add_instr(args->instrs, call);

    for (i = 0; i < args->args_count; ++i)
    {
        struct hlsl_ir_var *param = func->parameters.vars[i];
        struct hlsl_ir_node *arg = args->args[i];

        if (param->storage_modifiers & HLSL_STORAGE_OUT)
        {
            struct hlsl_ir_node *load;

            if (arg->data_type->modifiers & HLSL_MODIFIER_CONST)
                hlsl_error(ctx, &arg->loc, VKD3D_SHADER_ERROR_HLSL_MODIFIES_CONST,
                        "Output argument to \"%s\" is const.", func->func->name);

            load = hlsl_block_add_simple_load(ctx, args->instrs, param, &arg->loc);
            if (!add_assignment(ctx, args->instrs, arg, ASSIGN_OP_ASSIGN, load, true))
                return NULL;
        }
    }

    if (func->return_var)
        hlsl_block_add_simple_load(ctx, args->instrs, func->return_var, loc);
    else
        add_void_expr(ctx, args->instrs, loc);

    return call;
}

static struct hlsl_ir_node *intrinsic_float_convert_arg(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, struct hlsl_ir_node *arg, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type = arg->data_type;

    if (!hlsl_type_is_integer(type))
        return arg;

    type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_FLOAT, type->e.numeric.dimx, type->e.numeric.dimy);
    return add_implicit_conversion(ctx, params->instrs, arg, type, loc);
}

static void convert_args(struct hlsl_ctx *ctx, const struct parse_initializer *params,
        struct hlsl_type *type, const struct vkd3d_shader_location *loc)
{
    unsigned int i;

    for (i = 0; i < params->args_count; ++i)
        params->args[i] = add_implicit_conversion(ctx, params->instrs, params->args[i], type, loc);
}

static struct hlsl_type *elementwise_intrinsic_get_common_type(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type base = params->args[0]->data_type->e.numeric.type;
    bool vectors = false, matrices = false;
    unsigned int dimx = 4, dimy = 4;
    struct hlsl_type *common_type;
    unsigned int i;

    for (i = 0; i < params->args_count; ++i)
    {
        struct hlsl_type *arg_type = params->args[i]->data_type;

        base = expr_common_base_type(base, arg_type->e.numeric.type);

        if (arg_type->class == HLSL_CLASS_VECTOR)
        {
            vectors = true;
            dimx = min(dimx, arg_type->e.numeric.dimx);
        }
        else if (arg_type->class == HLSL_CLASS_MATRIX)
        {
            matrices = true;
            dimx = min(dimx, arg_type->e.numeric.dimx);
            dimy = min(dimy, arg_type->e.numeric.dimy);
        }
    }

    if (matrices && vectors)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Cannot use both matrices and vectors in an elementwise intrinsic.");
        return NULL;
    }
    else if (matrices)
    {
        common_type = hlsl_get_matrix_type(ctx, base, dimx, dimy);
    }
    else if (vectors)
    {
        common_type = hlsl_get_vector_type(ctx, base, dimx);
    }
    else
    {
        common_type = hlsl_get_scalar_type(ctx, base);
    }

    return common_type;
}

static bool elementwise_intrinsic_convert_args(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *common_type;

    if (!(common_type = elementwise_intrinsic_get_common_type(ctx, params, loc)))
        return false;

    convert_args(ctx, params, common_type, loc);
    return true;
}

static bool elementwise_intrinsic_float_convert_args(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type;

    if (!(type = elementwise_intrinsic_get_common_type(ctx, params, loc)))
        return false;
    if (hlsl_type_is_integer(type))
        type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_FLOAT, type->e.numeric.dimx, type->e.numeric.dimy);

    convert_args(ctx, params, type, loc);
    return true;
}

static bool elementwise_intrinsic_int_convert_args(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type;

    if (!(type = elementwise_intrinsic_get_common_type(ctx, params, loc)))
        return false;

    type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_INT, type->e.numeric.dimx, type->e.numeric.dimy);

    convert_args(ctx, params, type, loc);
    return true;
}

static bool elementwise_intrinsic_uint_convert_args(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type;

    if (!(type = elementwise_intrinsic_get_common_type(ctx, params, loc)))
        return false;

    type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_UINT, type->e.numeric.dimx, type->e.numeric.dimy);

    convert_args(ctx, params, type, loc);
    return true;
}

static bool intrinsic_abs(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *type = params->args[0]->data_type;

    if (!hlsl_type_is_floating_point(type) && !hlsl_type_is_signed_integer(type))
        return true;

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_ABS, params->args[0], loc);
}

static bool write_acos_or_asin(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc, bool asin_mode)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_ir_node *arg;
    struct hlsl_type *type;
    char *body;

    static const char template[] =
            "%s %s(%s x)\n"
            "{\n"
            "    %s abs_arg = abs(x);\n"
            "    %s poly_approx = (((-0.018729\n"
            "        * abs_arg + 0.074261)\n"
            "        * abs_arg - 0.212114)\n"
            "        * abs_arg + 1.570729);\n"
            "    %s correction = sqrt(1.0 - abs_arg);\n"
            "    %s zero_flip = (x < 0.0) * (-2.0 * correction * poly_approx + 3.141593);\n"
            "    %s result = poly_approx * correction + zero_flip;\n"
            "    return %s;\n"
            "}";
    static const char fn_name_acos[] = "acos";
    static const char fn_name_asin[] = "asin";
    static const char return_stmt_acos[] = "result";
    static const char return_stmt_asin[] = "-result + 1.570796";

    const char *fn_name = asin_mode ? fn_name_asin : fn_name_acos;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);
    type = arg->data_type;

    if (!(body = hlsl_sprintf_alloc(ctx, template,
            type->name, fn_name, type->name,
            type->name, type->name, type->name, type->name, type->name,
            (asin_mode ? return_stmt_asin : return_stmt_acos))))
        return false;
    func = hlsl_compile_internal_function(ctx, fn_name, body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_acos(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return write_acos_or_asin(ctx, params, loc, false);
}

/* Find the type corresponding to the given source type, with the same
 * dimensions but a different base type. */
static struct hlsl_type *convert_numeric_type(const struct hlsl_ctx *ctx,
        const struct hlsl_type *type, enum hlsl_base_type base_type)
{
    return hlsl_get_numeric_type(ctx, type->class, base_type, type->e.numeric.dimx, type->e.numeric.dimy);
}

static void add_combine_components(struct hlsl_ctx *ctx, const struct parse_initializer *params,
        struct hlsl_ir_node *arg, enum hlsl_ir_expr_op op, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *res, *load;
    unsigned int i, count;

    count = hlsl_type_component_count(arg->data_type);

    res = hlsl_add_load_component(ctx, params->instrs, arg, 0, loc);

    for (i = 1; i < count; ++i)
    {
        load = hlsl_add_load_component(ctx, params->instrs, arg, i, loc);
        res = hlsl_block_add_binary_expr(ctx, params->instrs, op, res, load);
    }
}

static bool intrinsic_all(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg = params->args[0], *cast;
    struct hlsl_type *bool_type;

    bool_type = convert_numeric_type(ctx, arg->data_type, HLSL_TYPE_BOOL);
    cast = add_cast(ctx, params->instrs, arg, bool_type, loc);
    add_combine_components(ctx, params, cast, HLSL_OP2_LOGIC_AND, loc);
    return true;
}

static bool intrinsic_any(struct hlsl_ctx *ctx, const struct parse_initializer *params,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg = params->args[0], *cast;
    struct hlsl_type *bool_type;

    bool_type = convert_numeric_type(ctx, arg->data_type, HLSL_TYPE_BOOL);
    cast = add_cast(ctx, params->instrs, arg, bool_type, loc);
    add_combine_components(ctx, params, cast, HLSL_OP2_LOGIC_OR, loc);
    return true;
}

static bool intrinsic_asin(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return write_acos_or_asin(ctx, params, loc, true);
}

static bool write_atan_or_atan2(struct hlsl_ctx *ctx,
        const struct parse_initializer *params,
        const struct vkd3d_shader_location *loc, bool atan2_mode)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_type *type;
    struct vkd3d_string_buffer *buf;
    int ret;

    static const char atan2_name[] = "atan2";
    static const char atan_name[] = "atan";

    static const char atan2_header_template[] =
            "%s atan2(%s y, %s x)\n"
            "{\n"
            "    %s in_y, in_x;\n"
            "    in_y = y;\n"
            "    in_x = x;\n";
    static const char atan_header_template[] =
            "%s atan(%s y)\n"
            "{\n"
            "    %s in_y, in_x;\n"
            "    in_y = y;\n"
            "    in_x = 1.0;\n";

    static const char body_template[] =
            "    %s recip, input, x2, poly_approx, flipped;"
            "    recip = 1.0 / max(abs(in_y), abs(in_x));\n"
            "    input = recip * min(abs(in_y), abs(in_x));\n"
            "    x2 = input * input;\n"
            "    poly_approx = ((((0.020835\n"
            "        * x2 - 0.085133)\n"
            "        * x2 + 0.180141)\n"
            "        * x2 - 0.330299)\n"
            "        * x2 + 0.999866)\n"
            "        * input;\n"
            "    flipped = poly_approx * -2.0 + 1.570796;\n"
            "    poly_approx += abs(in_x) < abs(in_y) ? flipped : 0.0;\n"
            "    poly_approx += in_x < 0.0 ? -3.1415927 : 0.0;\n"
            "    return (min(in_x, in_y) < 0.0 && max(in_x, in_y) >= 0.0)\n"
            "        ? -poly_approx\n"
            "        : poly_approx;\n"
            "}";

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (!(buf = hlsl_get_string_buffer(ctx)))
        return false;

    if (atan2_mode)
        ret = vkd3d_string_buffer_printf(buf, atan2_header_template,
                type->name, type->name, type->name, type->name);
    else
        ret = vkd3d_string_buffer_printf(buf, atan_header_template,
                type->name, type->name, type->name);
    if (ret < 0)
    {
        hlsl_release_string_buffer(ctx, buf);
        return false;
    }

    ret = vkd3d_string_buffer_printf(buf, body_template, type->name);
    if (ret < 0)
    {
        hlsl_release_string_buffer(ctx, buf);
        return false;
    }

    func = hlsl_compile_internal_function(ctx,
            atan2_mode ? atan2_name : atan_name, buf->buffer);
    hlsl_release_string_buffer(ctx, buf);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_atan(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return write_atan_or_atan2(ctx, params, loc, false);
}


static bool intrinsic_atan2(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return write_atan_or_atan2(ctx, params, loc, true);
}

static bool intrinsic_asfloat(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *data_type;

    data_type = params->args[0]->data_type;
    if (data_type->e.numeric.type == HLSL_TYPE_BOOL || data_type->e.numeric.type == HLSL_TYPE_DOUBLE)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, data_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong argument type of asfloat(): expected 'int', 'uint', 'float', or 'half', but got '%s'.",
                    string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }
    data_type = convert_numeric_type(ctx, data_type, HLSL_TYPE_FLOAT);

    operands[0] = params->args[0];
    return add_expr(ctx, params->instrs, HLSL_OP1_REINTERPRET, operands, data_type, loc);
}

static bool intrinsic_asint(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *data_type;

    data_type = params->args[0]->data_type;
    if (data_type->e.numeric.type == HLSL_TYPE_BOOL || data_type->e.numeric.type == HLSL_TYPE_DOUBLE)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, data_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong argument type of asint(): expected 'int', 'uint', 'float', or 'half', but got '%s'.",
                    string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }
    data_type = convert_numeric_type(ctx, data_type, HLSL_TYPE_INT);

    operands[0] = params->args[0];
    return add_expr(ctx, params->instrs, HLSL_OP1_REINTERPRET, operands, data_type, loc);
}

static bool intrinsic_asuint(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *data_type;

    if (params->args_count != 1 && params->args_count != 3)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to function 'asuint': expected 1 or 3, but got %u.", params->args_count);
        return false;
    }

    if (params->args_count == 3)
    {
        hlsl_fixme(ctx, loc, "Double-to-integer conversion.");
        return false;
    }

    data_type = params->args[0]->data_type;
    if (data_type->e.numeric.type == HLSL_TYPE_BOOL || data_type->e.numeric.type == HLSL_TYPE_DOUBLE)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, data_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong type for argument 0 of asuint(): expected 'int', 'uint', 'float', or 'half', but got '%s'.",
                    string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }
    data_type = convert_numeric_type(ctx, data_type, HLSL_TYPE_UINT);

    operands[0] = params->args[0];
    return add_expr(ctx, params->instrs, HLSL_OP1_REINTERPRET, operands, data_type, loc);
}

static bool intrinsic_ceil(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_CEIL, arg, loc);
}

static bool intrinsic_clamp(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *max;

    if (!elementwise_intrinsic_convert_args(ctx, params, loc))
        return false;

    if (!(max = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MAX, params->args[0], params->args[1], loc)))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MIN, max, params->args[2], loc);
}

static bool intrinsic_clip(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *condition;

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;

    condition = params->args[0];

    if (ctx->profile->major_version < 4 && hlsl_type_component_count(condition->data_type) > 4)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, condition->data_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Argument type cannot exceed 4 components, got type \"%s\".", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    hlsl_block_add_jump(ctx, params->instrs, HLSL_IR_JUMP_DISCARD_NEG, condition, loc);
    return true;
}

static bool intrinsic_cos(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_COS, arg, loc);
}

static bool write_cosh_or_sinh(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc, bool sinh_mode)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_ir_node *arg;
    const char *fn_name, *type_name;
    char *body;

    static const char template[] =
            "%s %s(%s x)\n"
            "{\n"
            "    return (exp(x) %s exp(-x)) / 2;\n"
            "}\n";
    static const char fn_name_sinh[] = "sinh";
    static const char fn_name_cosh[] = "cosh";

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    type_name = arg->data_type->name;
    fn_name = sinh_mode ? fn_name_sinh : fn_name_cosh;

    if (!(body = hlsl_sprintf_alloc(ctx, template,
            type_name, fn_name, type_name, sinh_mode ? "-" : "+")))
        return false;

    func = hlsl_compile_internal_function(ctx, fn_name, body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_cosh(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return write_cosh_or_sinh(ctx, params, loc, false);
}

static bool intrinsic_countbits(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *type;

    if (!elementwise_intrinsic_uint_convert_args(ctx, params, loc))
        return false;
    type = convert_numeric_type(ctx, params->args[0]->data_type, HLSL_TYPE_UINT);

    operands[0] = params->args[0];
    return add_expr(ctx, params->instrs, HLSL_OP1_COUNTBITS, operands, type, loc);
}

static bool intrinsic_cross(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg1_swzl1, *arg1_swzl2, *arg2_swzl1, *arg2_swzl2;
    struct hlsl_ir_node *arg1 = params->args[0], *arg2 = params->args[1];
    struct hlsl_ir_node *arg1_cast, *arg2_cast, *mul1_neg, *mul1, *mul2;
    struct hlsl_type *cast_type;
    enum hlsl_base_type base;

    base = expr_common_base_type(arg1->data_type->e.numeric.type, arg2->data_type->e.numeric.type);
    if (hlsl_base_type_is_integer(base))
        base = HLSL_TYPE_FLOAT;

    cast_type = hlsl_get_vector_type(ctx, base, 3);

    arg1_cast = add_implicit_conversion(ctx, params->instrs, arg1, cast_type, loc);
    arg2_cast = add_implicit_conversion(ctx, params->instrs, arg2, cast_type, loc);
    arg1_swzl1 = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(Z, X, Y, Z), 3, arg1_cast, loc);
    arg2_swzl1 = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(Y, Z, X, Y), 3, arg2_cast, loc);

    if (!(mul1 = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, arg1_swzl1, arg2_swzl1, loc)))
        return false;

    mul1_neg = hlsl_block_add_unary_expr(ctx, params->instrs, HLSL_OP1_NEG, mul1, loc);

    arg1_swzl2 = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(Y, Z, X, Y), 3, arg1_cast, loc);
    arg2_swzl2 = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(Z, X, Y, Z), 3, arg2_cast, loc);

    if (!(mul2 = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, arg1_swzl2, arg2_swzl2, loc)))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_ADD, mul2, mul1_neg, loc);
}

static bool intrinsic_ddx(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_DSX, arg, loc);
}

static bool intrinsic_ddx_coarse(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_DSX_COARSE, arg, loc);
}

static bool intrinsic_ddx_fine(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_DSX_FINE, arg, loc);
}

static bool intrinsic_ddy(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_DSY, arg, loc);
}

static bool intrinsic_ddy_coarse(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_DSY_COARSE, arg, loc);
}

static bool intrinsic_degrees(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg, *deg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    /* 1 rad = 180/pi degree = 57.2957795 degree */
    deg = hlsl_block_add_float_constant(ctx, params->instrs, 57.2957795f, loc);
    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, arg, deg, loc);
}

static bool intrinsic_ddy_fine(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_DSY_FINE, arg, loc);
}

static bool intrinsic_determinant(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    static const char determinant2x2[] =
            "%s determinant(%s2x2 m)\n"
            "{\n"
            "    return m._11 * m._22 - m._12 * m._21;\n"
            "}";
    static const char determinant3x3[] =
            "%s determinant(%s3x3 m)\n"
            "{\n"
            "    %s2x2 m1 = { m._22, m._23, m._32, m._33 };\n"
            "    %s2x2 m2 = { m._21, m._23, m._31, m._33 };\n"
            "    %s2x2 m3 = { m._21, m._22, m._31, m._32 };\n"
            "    %s3 v1 = { m._11, -m._12, m._13 };\n"
            "    %s3 v2 = { determinant(m1), determinant(m2), determinant(m3) };\n"
            "    return dot(v1, v2);\n"
            "}";
    static const char determinant4x4[] =
            "%s determinant(%s4x4 m)\n"
            "{\n"
            "    %s3x3 m1 = { m._22, m._23, m._24, m._32, m._33, m._34, m._42, m._43, m._44 };\n"
            "    %s3x3 m2 = { m._21, m._23, m._24, m._31, m._33, m._34, m._41, m._43, m._44 };\n"
            "    %s3x3 m3 = { m._21, m._22, m._24, m._31, m._32, m._34, m._41, m._42, m._44 };\n"
            "    %s3x3 m4 = { m._21, m._22, m._23, m._31, m._32, m._33, m._41, m._42, m._43 };\n"
            "    %s4 v1 = { m._11, -m._12, m._13, -m._14 };\n"
            "    %s4 v2 = { determinant(m1), determinant(m2), determinant(m3), determinant(m4) };\n"
            "    return dot(v1, v2);\n"
            "}";
    static const char *templates[] =
    {
        [2] = determinant2x2,
        [3] = determinant3x3,
        [4] = determinant4x4,
    };

    struct hlsl_ir_node *arg = params->args[0];
    const struct hlsl_type *type = arg->data_type;
    struct hlsl_ir_function_decl *func;
    const char *typename, *template;
    unsigned int dim;
    char *body;

    if (type->class != HLSL_CLASS_SCALAR && type->class != HLSL_CLASS_MATRIX)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Invalid argument type.");
        return false;
    }

    arg = intrinsic_float_convert_arg(ctx, params, arg, loc);

    dim = min(type->e.numeric.dimx, type->e.numeric.dimy);
    if (dim == 1)
        return hlsl_add_load_component(ctx, params->instrs, arg, 0, loc);

    typename = hlsl_get_scalar_type(ctx, arg->data_type->e.numeric.type)->name;
    template = templates[dim];

    switch (dim)
    {
        case 2:
            body = hlsl_sprintf_alloc(ctx, template, typename, typename);
            break;
        case 3:
            body = hlsl_sprintf_alloc(ctx, template, typename, typename, typename,
                    typename, typename, typename, typename);
            break;
        case 4:
            body = hlsl_sprintf_alloc(ctx, template, typename, typename, typename,
                    typename, typename, typename, typename, typename);
            break;
        default:
            vkd3d_unreachable();
    }

    if (!body)
        return false;

    func = hlsl_compile_internal_function(ctx, "determinant", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_distance(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg1, *arg2, *neg, *add, *dot;

    arg1 = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);
    arg2 = intrinsic_float_convert_arg(ctx, params, params->args[1], loc);

    if (!(neg = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_NEG, arg2, loc)))
        return false;

    if (!(add = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_ADD, arg1, neg, loc)))
        return false;

    if (!(dot = add_binary_dot_expr(ctx, params->instrs, add, add, loc)))
        return false;

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_SQRT, dot, loc);
}

static bool intrinsic_dot(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return !!add_binary_dot_expr(ctx, params->instrs, params->args[0], params->args[1], loc);
}

static bool intrinsic_dst(struct hlsl_ctx *ctx, const struct parse_initializer *params,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_type *type, *vec4_type;
    char *body;

    static const char template[] =
            "%s dst(%s i0, %s i1)\n"
            "{\n"
            /* Scalars and vector-4s are both valid inputs, so promote scalars
             * if necessary. */
            "    %s src0 = i0, src1 = i1;\n"
            "    return %s(1, src0.y * src1.y, src0.z, src1.w);\n"
            "}";

    if (!elementwise_intrinsic_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;
    if (!(type->class == HLSL_CLASS_SCALAR
            || (type->class == HLSL_CLASS_VECTOR && type->e.numeric.dimx == 4)))
    {
        struct vkd3d_string_buffer *string;
        if ((string = hlsl_type_to_string(ctx, type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong dimension for dst(): expected scalar or 4-dimensional vector, but got %s.",
                    string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }
    vec4_type = hlsl_get_vector_type(ctx, type->e.numeric.type, 4);

    if (!(body = hlsl_sprintf_alloc(ctx, template,
            vec4_type->name, type->name, type->name,
            vec4_type->name,
            vec4_type->name)))
        return false;
    func = hlsl_compile_internal_function(ctx, "dst", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_exp(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg, *mul, *coeff;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    /* 1/ln(2) */
    coeff = hlsl_block_add_float_constant(ctx, params->instrs, 1.442695f, loc);

    if (!(mul = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, coeff, arg, loc)))
        return false;

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_EXP2, mul, loc);
}

static bool intrinsic_exp2(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_EXP2, arg, loc);
}

static bool intrinsic_faceforward(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_type *type;
    char *body;

    static const char template[] =
            "%s faceforward(%s n, %s i, %s ng)\n"
            "{\n"
            "    return dot(i, ng) < 0 ? n : -n;\n"
            "}\n";

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (!(body = hlsl_sprintf_alloc(ctx, template,
            type->name, type->name, type->name, type->name)))
        return false;
    func = hlsl_compile_internal_function(ctx, "faceforward", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_f16tof32(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *type;

    if (!elementwise_intrinsic_uint_convert_args(ctx, params, loc))
        return false;

    type = convert_numeric_type(ctx, params->args[0]->data_type, HLSL_TYPE_FLOAT);

    operands[0] = params->args[0];
    return add_expr(ctx, params->instrs, HLSL_OP1_F16TOF32, operands, type, loc);
}

static bool intrinsic_f32tof16(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *type;

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;

    type = convert_numeric_type(ctx, params->args[0]->data_type, HLSL_TYPE_UINT);

    operands[0] = params->args[0];
    return add_expr(ctx, params->instrs, HLSL_OP1_F32TOF16, operands, type, loc);
}

static bool intrinsic_firstbithigh(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *type = params->args[0]->data_type;
    struct hlsl_ir_node *c, *clz, *eq, *xor;

    if (hlsl_version_lt(ctx, 4, 0))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INCOMPATIBLE_PROFILE,
                "The 'firstbithigh' intrinsic requires shader model 4.0 or higher.");

    if (hlsl_type_is_unsigned_integer(type))
    {
        if (!elementwise_intrinsic_uint_convert_args(ctx, params, loc))
            return false;
    }
    else
    {
        if (!elementwise_intrinsic_int_convert_args(ctx, params, loc))
            return false;
    }
    type = convert_numeric_type(ctx, type, HLSL_TYPE_UINT);

    operands[0] = params->args[0];
    if (hlsl_version_lt(ctx, 5, 0))
        return add_expr(ctx, params->instrs, HLSL_OP1_FIND_MSB, operands, type, loc);

    c = hlsl_block_add_uint_constant(ctx, params->instrs, 0x1f, loc);

    if (!(clz = add_expr(ctx, params->instrs, HLSL_OP1_CLZ, operands, type, loc)))
        return false;
    if (!(xor = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_BIT_XOR, c, clz, loc)))
        return false;

    c = hlsl_block_add_uint_constant(ctx, params->instrs, ~0u, loc);

    if (!(eq = add_binary_comparison_expr(ctx, params->instrs, HLSL_OP2_EQUAL, clz, c, loc)))
        return false;

    operands[0] = eq;
    operands[1] = add_implicit_conversion(ctx, params->instrs, c, type, loc);
    operands[2] = xor;
    return add_expr(ctx, params->instrs, HLSL_OP3_TERNARY, operands, type, loc);
}

static bool intrinsic_firstbitlow(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *type;

    if (hlsl_version_lt(ctx, 4, 0))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INCOMPATIBLE_PROFILE,
                "The 'firstbitlow' intrinsic requires shader model 4.0 or higher.");

    if (!elementwise_intrinsic_uint_convert_args(ctx, params, loc))
        return false;
    type = convert_numeric_type(ctx, params->args[0]->data_type, HLSL_TYPE_UINT);

    operands[0] = params->args[0];
    return add_expr(ctx, params->instrs, HLSL_OP1_CTZ, operands, type, loc);
}

static bool intrinsic_floor(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_FLOOR, arg, loc);
}

static bool intrinsic_fmod(struct hlsl_ctx *ctx, const struct parse_initializer *params,
        const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *x, *y, *div, *abs, *frac, *neg_frac, *ge, *select, *zero;
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = { 0 };
    static const struct hlsl_constant_value zero_value;

    x = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);
    y = intrinsic_float_convert_arg(ctx, params, params->args[1], loc);

    if (!(div = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_DIV, x, y, loc)))
        return false;

    zero = hlsl_block_add_constant(ctx, params->instrs, div->data_type, &zero_value, loc);

    if (!(abs = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_ABS, div, loc)))
        return false;

    if (!(frac = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_FRACT, abs, loc)))
        return false;

    if (!(neg_frac = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_NEG, frac, loc)))
        return false;

    if (!(ge = add_binary_comparison_expr(ctx, params->instrs, HLSL_OP2_GEQUAL, div, zero, loc)))
        return false;

    operands[0] = ge;
    operands[1] = frac;
    operands[2] = neg_frac;
    if (!(select = add_expr(ctx, params->instrs, HLSL_OP3_TERNARY, operands, x->data_type, loc)))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, select, y, loc);
}

static bool intrinsic_frac(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_FRACT, arg, loc);
}

static bool intrinsic_frexp(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type, *uint_dim_type, *int_dim_type, *bool_dim_type;
    struct hlsl_ir_function_decl *func;
    char *body;

    static const char template[] =
            "%s frexp(%s x, out %s exp)\n"
            "{\n"
            /* If x is zero, always return zero for exp and mantissa. */
            "    %s is_nonzero_mask = x != 0.0;\n"
            "    %s bits = asuint(x);\n"
            /* Subtract 126, not 127, to increase the exponent */
            "    %s exp_int = asint((bits & 0x7f800000u) >> 23) - 126;\n"
            /* Clear the given exponent and replace it with the bit pattern
             * for 2^-1 */
            "    %s mantissa = asfloat((bits & 0x007fffffu) | 0x3f000000);\n"
            "    exp = is_nonzero_mask * %s(exp_int);\n"
            "    return is_nonzero_mask * mantissa;\n"
            "}\n";

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (type->e.numeric.type == HLSL_TYPE_DOUBLE)
    {
        hlsl_fixme(ctx, loc, "frexp() on doubles.");
        return false;
    }
    type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_FLOAT, type->e.numeric.dimx, type->e.numeric.dimy);
    uint_dim_type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_UINT, type->e.numeric.dimx, type->e.numeric.dimy);
    int_dim_type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_INT, type->e.numeric.dimx, type->e.numeric.dimy);
    bool_dim_type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_BOOL, type->e.numeric.dimx, type->e.numeric.dimy);

    if (!(body = hlsl_sprintf_alloc(ctx, template, type->name, type->name, type->name,
            bool_dim_type->name, uint_dim_type->name, int_dim_type->name, type->name, type->name)))
        return false;
    func = hlsl_compile_internal_function(ctx, "frexp", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_fwidth(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_type *type;
    char *body;

    static const char template[] =
            "%s fwidth(%s x)\n"
            "{\n"
            "    return abs(ddx(x)) + abs(ddy(x));\n"
            "}";

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (!(body = hlsl_sprintf_alloc(ctx, template, type->name, type->name)))
        return false;
    func = hlsl_compile_internal_function(ctx, "fwidth", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_isinf(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type = params->args[0]->data_type, *bool_type;
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};

    bool_type = hlsl_get_numeric_type(ctx, type->class, HLSL_TYPE_BOOL,
            type->e.numeric.dimx, type->e.numeric.dimy);

    args[0] = params->args[0];
    return !!add_expr(ctx, params->instrs, HLSL_OP1_ISINF, args, bool_type, loc);
}

static bool intrinsic_ldexp(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;

    if (!(arg = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_EXP2, params->args[1], loc)))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, params->args[0], arg, loc);
}

static bool intrinsic_length(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type = params->args[0]->data_type;
    struct hlsl_ir_node *arg, *dot;

    if (type->class == HLSL_CLASS_MATRIX)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Invalid type %s.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    if (!(dot = add_binary_dot_expr(ctx, params->instrs, arg, arg, loc)))
        return false;

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_SQRT, dot, loc);
}

static bool intrinsic_lerp(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *neg, *add, *mul;

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;

    if (!(neg = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_NEG, params->args[0], loc)))
        return false;

    if (!(add = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_ADD, params->args[1], neg, loc)))
        return false;

    if (!(mul = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, params->args[2], add, loc)))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_ADD, params->args[0], mul, loc);
}

static bool intrinsic_lit(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *func;

    static const char body[] =
            "float4 lit(float n_l, float n_h, float m)\n"
            "{\n"
            "    float4 ret;\n"
            "    ret.xw = 1.0;\n"
            "    ret.y = max(n_l, 0);\n"
            "    ret.z = (n_l < 0 || n_h < 0) ? 0 : pow(n_h, m);\n"
            "    return ret;\n"
            "}";

    if (params->args[0]->data_type->class != HLSL_CLASS_SCALAR
            || params->args[1]->data_type->class != HLSL_CLASS_SCALAR
            || params->args[2]->data_type->class != HLSL_CLASS_SCALAR)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Invalid argument type.");
        return false;
    }

    if (!(func = hlsl_compile_internal_function(ctx, "lit", body)))
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_log(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *log, *arg, *coeff;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    if (!(log = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_LOG2, arg, loc)))
        return false;

    /* ln(2) */
    coeff = hlsl_block_add_float_constant(ctx, params->instrs, 0.69314718055f, loc);
    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, log, coeff, loc);
}

static bool intrinsic_log10(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *log, *arg, *coeff;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    if (!(log = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_LOG2, arg, loc)))
        return false;

    /* 1 / log2(10) */
    coeff = hlsl_block_add_float_constant(ctx, params->instrs, 0.301029996f, loc);
    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, log, coeff, loc);
}

static bool intrinsic_log2(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_LOG2, arg, loc);
}

static bool intrinsic_mad(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};

    if (!elementwise_intrinsic_convert_args(ctx, params, loc))
        return false;

    args[0] = params->args[0];
    args[1] = params->args[1];
    args[2] = params->args[2];
    return add_expr(ctx, params->instrs, HLSL_OP3_MAD, args, args[0]->data_type, loc);
}

static bool intrinsic_max(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    if (!elementwise_intrinsic_convert_args(ctx, params, loc))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MAX, params->args[0], params->args[1], loc);
}

static bool intrinsic_min(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    if (!elementwise_intrinsic_convert_args(ctx, params, loc))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MIN, params->args[0], params->args[1], loc);
}

static bool intrinsic_modf(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_type *type;
    char *body;

    static const char template[] =
            "%s modf(%s x, out %s ip)\n"
            "{\n"
            "    ip = trunc(x);\n"
            "    return x - ip;\n"
            "}";

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (!(body = hlsl_sprintf_alloc(ctx, template,
            type->name, type->name, type->name)))
        return false;
    func = hlsl_compile_internal_function(ctx, "modf", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_mul(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg1 = params->args[0], *arg2 = params->args[1], *cast1, *cast2;
    enum hlsl_base_type base = expr_common_base_type(arg1->data_type->e.numeric.type, arg2->data_type->e.numeric.type);
    struct hlsl_type *cast_type1 = arg1->data_type, *cast_type2 = arg2->data_type, *matrix_type, *ret_type;
    unsigned int i, j, k, vect_count = 0;
    struct hlsl_deref var_deref;
    struct hlsl_ir_node *load;
    struct hlsl_ir_var *var;

    if (arg1->data_type->class == HLSL_CLASS_SCALAR || arg2->data_type->class == HLSL_CLASS_SCALAR)
        return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, arg1, arg2, loc);

    if (arg1->data_type->class == HLSL_CLASS_VECTOR)
    {
        vect_count++;
        cast_type1 = hlsl_get_matrix_type(ctx, base, arg1->data_type->e.numeric.dimx, 1);
    }
    if (arg2->data_type->class == HLSL_CLASS_VECTOR)
    {
        vect_count++;
        cast_type2 = hlsl_get_matrix_type(ctx, base, 1, arg2->data_type->e.numeric.dimx);
    }

    matrix_type = hlsl_get_matrix_type(ctx, base, cast_type2->e.numeric.dimx, cast_type1->e.numeric.dimy);

    if (vect_count == 0)
    {
        ret_type = matrix_type;
    }
    else if (vect_count == 1)
    {
        VKD3D_ASSERT(matrix_type->e.numeric.dimx == 1 || matrix_type->e.numeric.dimy == 1);
        ret_type = hlsl_get_vector_type(ctx, base, matrix_type->e.numeric.dimx * matrix_type->e.numeric.dimy);
    }
    else
    {
        VKD3D_ASSERT(matrix_type->e.numeric.dimx == 1 && matrix_type->e.numeric.dimy == 1);
        ret_type = hlsl_get_scalar_type(ctx, base);
    }

    cast1 = add_implicit_conversion(ctx, params->instrs, arg1, cast_type1, loc);
    cast2 = add_implicit_conversion(ctx, params->instrs, arg2, cast_type2, loc);

    if (!(var = hlsl_new_synthetic_var(ctx, "mul", matrix_type, loc)))
        return false;
    hlsl_init_simple_deref_from_var(&var_deref, var);

    for (i = 0; i < matrix_type->e.numeric.dimx; ++i)
    {
        for (j = 0; j < matrix_type->e.numeric.dimy; ++j)
        {
            struct hlsl_ir_node *instr = NULL;

            for (k = 0; k < cast_type1->e.numeric.dimx && k < cast_type2->e.numeric.dimy; ++k)
            {
                struct hlsl_ir_node *value1, *value2, *mul;

                value1 = hlsl_add_load_component(ctx, params->instrs,
                        cast1, j * cast1->data_type->e.numeric.dimx + k, loc);
                value2 = hlsl_add_load_component(ctx, params->instrs,
                        cast2, k * cast2->data_type->e.numeric.dimx + i, loc);

                if (!(mul = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, value1, value2, loc)))
                    return false;

                if (instr)
                {
                    if (!(instr = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_ADD, instr, mul, loc)))
                        return false;
                }
                else
                {
                    instr = mul;
                }
            }

            hlsl_block_add_store_component(ctx, params->instrs, &var_deref,
                    j * matrix_type->e.numeric.dimx + i, instr);
        }
    }

    load = hlsl_block_add_simple_load(ctx, params->instrs, var, loc);
    add_implicit_conversion(ctx, params->instrs, load, ret_type, loc);
    return true;
}

static bool intrinsic_noise(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type = params->args[0]->data_type, *ret_type;
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};

    type = params->args[0]->data_type;
    if (type->class == HLSL_CLASS_MATRIX)
    {
        struct vkd3d_string_buffer *string;
        if ((string = hlsl_type_to_string(ctx, type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong argument type for noise(): expected vector or scalar, but got '%s'.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }

    args[0] = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);
    ret_type = hlsl_get_scalar_type(ctx, args[0]->data_type->e.numeric.type);

    return !!add_expr(ctx, params->instrs, HLSL_OP1_NOISE, args, ret_type, loc);
}

static bool intrinsic_normalize(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type = params->args[0]->data_type;
    struct hlsl_ir_node *dot, *rsq, *arg;

    if (type->class == HLSL_CLASS_MATRIX)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Invalid type %s.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    if (!(dot = add_binary_dot_expr(ctx, params->instrs, arg, arg, loc)))
        return false;

    if (!(rsq = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_RSQ, dot, loc)))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, rsq, arg, loc);
}

static bool intrinsic_pow(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *log, *mul;

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;

    if (!(log = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_LOG2, params->args[0], loc)))
        return NULL;

    if (!(mul = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, params->args[1], log, loc)))
        return NULL;

    return add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_EXP2, mul, loc);
}

static bool intrinsic_radians(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg, *rad;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    /* 1 degree = pi/180 rad = 0.0174532925f rad */
    rad = hlsl_block_add_float_constant(ctx, params->instrs, 0.0174532925f, loc);
    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, arg, rad, loc);
}

static bool intrinsic_rcp(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_RCP, arg, loc);
}

static bool intrinsic_reflect(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *i = params->args[0], *n = params->args[1];
    struct hlsl_ir_node *dot, *mul_n, *two_dot, *neg;

    if (!(dot = add_binary_dot_expr(ctx, params->instrs, i, n, loc)))
        return false;

    if (!(two_dot = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_ADD, dot, dot, loc)))
        return false;

    if (!(mul_n = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, n, two_dot, loc)))
        return false;

    if (!(neg = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_NEG, mul_n, loc)))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_ADD, i, neg, loc);
}

static bool intrinsic_refract(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_type *type, *scalar_type;
    struct hlsl_ir_function_decl *func;
    char *body;

    static const char template[] =
            "%s refract(%s r, %s n, %s i)\n"
            "{\n"
            "    %s d, t;\n"
            "    d = dot(r, n);\n"
            "    t = 1 - i.x * i.x * (1 - d * d);\n"
            "    return t >= 0.0 ? i.x * r - (i.x * d + sqrt(t)) * n : 0;\n"
            "}";

    if (params->args[0]->data_type->class == HLSL_CLASS_MATRIX
            || params->args[1]->data_type->class == HLSL_CLASS_MATRIX
            || params->args[2]->data_type->class == HLSL_CLASS_MATRIX)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Matrix arguments are not supported.");
        return false;
    }

    /* This is technically not an elementwise intrinsic, but the first two
     * arguments are.
     * The third argument is a scalar, but can be passed as a vector,
     * which should generate an implicit truncation warning.
     * Cast down to scalar explicitly, then we can just use
     * elementwise_intrinsic_float_convert_args().
     * This may result in casting the scalar back to a vector,
     * which we will only use the first component of. */

    scalar_type = hlsl_get_scalar_type(ctx, params->args[2]->data_type->e.numeric.type);
    params->args[2] = add_implicit_conversion(ctx, params->instrs, params->args[2], scalar_type, loc);

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (!(body = hlsl_sprintf_alloc(ctx, template, type->name, type->name,
            type->name, type->name, scalar_type->name)))
        return false;

    func = hlsl_compile_internal_function(ctx, "refract", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_round(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_ROUND, arg, loc);
}

static bool intrinsic_rsqrt(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_RSQ, arg, loc);
}

static bool intrinsic_saturate(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_SAT, arg, loc);
}

static bool intrinsic_sign(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *lt, *neg, *op1, *op2, *zero, *arg = params->args[0];
    static const struct hlsl_constant_value zero_value;

    struct hlsl_type *int_type = hlsl_get_numeric_type(ctx, arg->data_type->class, HLSL_TYPE_INT,
            arg->data_type->e.numeric.dimx, arg->data_type->e.numeric.dimy);

    zero = hlsl_block_add_constant(ctx, params->instrs,
            hlsl_get_scalar_type(ctx, arg->data_type->e.numeric.type), &zero_value, loc);

    /* Check if 0 < arg, cast bool to int */

    if (!(lt = add_binary_comparison_expr(ctx, params->instrs, HLSL_OP2_LESS, zero, arg, loc)))
        return false;

    op1 = add_implicit_conversion(ctx, params->instrs, lt, int_type, loc);

    /* Check if arg < 0, cast bool to int and invert (meaning true is -1) */

    if (!(lt = add_binary_comparison_expr(ctx, params->instrs, HLSL_OP2_LESS, arg, zero, loc)))
        return false;

    op2 = add_implicit_conversion(ctx, params->instrs, lt, int_type, loc);

    if (!(neg = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_NEG, op2, loc)))
        return false;

    /* Adding these two together will make 1 when > 0, -1 when < 0, and 0 when neither */
    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_ADD, neg, op1, loc);
}

static bool intrinsic_sin(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_SIN, arg, loc);
}

static bool intrinsic_sincos(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_type *type;
    char *body;

    static const char template[] =
            "void sincos(%s f, out %s s, out %s c)\n"
            "{\n"
            "    s = sin(f);\n"
            "    c = cos(f);\n"
            "}";

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (!(body = hlsl_sprintf_alloc(ctx, template,
            type->name, type->name, type->name)))
        return false;
    func = hlsl_compile_internal_function(ctx, "sincos", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_sinh(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return write_cosh_or_sinh(ctx, params, loc, true);
}

/* smoothstep(a, b, x) = p^2 (3 - 2p), where p = saturate((x - a)/(b - a)) */
static bool intrinsic_smoothstep(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_type *type;
    char *body;

    static const char template[] =
            "%s smoothstep(%s low, %s high, %s x)\n"
            "{\n"
            "    %s p = saturate((x - low) / (high - low));\n"
            "    return (p * p) * (3 - 2 * p);\n"
            "}";

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (!(body = hlsl_sprintf_alloc(ctx, template, type->name, type->name, type->name, type->name, type->name)))
        return false;
    func = hlsl_compile_internal_function(ctx, "smoothstep", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_sqrt(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg;

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_SQRT, arg, loc);
}

static bool intrinsic_step(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *ge;
    struct hlsl_type *type;

    if (!elementwise_intrinsic_float_convert_args(ctx, params, loc))
        return false;
    type = params->args[0]->data_type;

    if (!(ge = add_binary_comparison_expr(ctx, params->instrs, HLSL_OP2_GEQUAL,
            params->args[1], params->args[0], loc)))
        return false;

    add_implicit_conversion(ctx, params->instrs, ge, type, loc);
    return true;
}

static bool intrinsic_tan(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg = params->args[0], *sin, *cos;

    if (!(sin = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_SIN, arg, loc)))
        return false;

    if (!(cos = add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_COS, arg, loc)))
        return false;

    return !!add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_DIV, sin, cos, loc);
}

static bool intrinsic_tanh(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_function_decl *func;
    struct hlsl_ir_node *arg;
    struct hlsl_type *type;
    char *body;

    static const char template[] =
            "%s tanh(%s x)\n"
            "{\n"
            "    %s exp_pos, exp_neg;\n"
            "    exp_pos = exp(x);\n"
            "    exp_neg = exp(-x);\n"
            "    return (exp_pos - exp_neg) / (exp_pos + exp_neg);\n"
            "}\n";

    arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);
    type = arg->data_type;

    if (!(body = hlsl_sprintf_alloc(ctx, template,
            type->name, type->name, type->name)))
        return false;

    func = hlsl_compile_internal_function(ctx, "tanh", body);
    vkd3d_free(body);
    if (!func)
        return false;

    return !!add_user_call(ctx, func, params, loc);
}

static bool intrinsic_tex(struct hlsl_ctx *ctx, const struct parse_initializer *params,
        const struct vkd3d_shader_location *loc, const char *name, enum hlsl_sampler_dim dim)
{
    unsigned int sampler_dim = hlsl_sampler_dim_count(dim);
    struct hlsl_resource_load_params load_params = { 0 };
    const struct hlsl_type *sampler_type;
    struct hlsl_ir_node *coords;

    if (params->args_count != 2 && params->args_count != 4)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to function '%s': expected 2 or 4, but got %u.", name, params->args_count);
        return false;
    }

    sampler_type = params->args[0]->data_type;
    if (sampler_type->class != HLSL_CLASS_SAMPLER
            || (sampler_type->sampler_dim != dim && sampler_type->sampler_dim != HLSL_SAMPLER_DIM_GENERIC))
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, sampler_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong type for argument 1 of '%s': expected 'sampler' or '%s', but got '%s'.",
                    name, ctx->builtin_types.sampler[dim]->name, string->buffer);
        hlsl_release_string_buffer(ctx, string);
    }

    if (!strcmp(name, "tex2Dbias")
            || !strcmp(name, "tex2Dlod")
            || !strcmp(name, "texCUBEbias"))
    {
        struct hlsl_ir_node *lod, *c;

        if (!strcmp(name, "tex2Dlod"))
            load_params.type = HLSL_RESOURCE_SAMPLE_LOD;
        else
            load_params.type = HLSL_RESOURCE_SAMPLE_LOD_BIAS;

        c = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(X, Y, Z, W), sampler_dim, params->args[1], loc);
        coords = add_implicit_conversion(ctx, params->instrs, c,
                hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);

        lod = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(W, W, W, W), 1, params->args[1], loc);
        load_params.lod = add_implicit_conversion(ctx, params->instrs, lod,
                hlsl_get_scalar_type(ctx, HLSL_TYPE_FLOAT), loc);
    }
    else if (!strcmp(name, "tex2Dproj")
            || !strcmp(name, "tex3Dproj")
            || !strcmp(name, "texCUBEproj"))
    {
        coords = add_implicit_conversion(ctx, params->instrs, params->args[1],
                hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, 4), loc);

        if (hlsl_version_ge(ctx, 4, 0))
        {
            struct hlsl_ir_node *divisor;

            divisor = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(W, W, W, W), sampler_dim, coords, loc);
            coords = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(X, Y, Z, W), sampler_dim, coords, loc);

            if (!(coords = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_DIV, coords, divisor, loc)))
                return false;

            load_params.type = HLSL_RESOURCE_SAMPLE;
        }
        else
        {
            load_params.type = HLSL_RESOURCE_SAMPLE_PROJ;
        }
    }
    else if (params->args_count == 4) /* Gradient sampling. */
    {
        coords = add_implicit_conversion(ctx, params->instrs, params->args[1],
                hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
        load_params.ddx = add_implicit_conversion(ctx, params->instrs, params->args[2],
                hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
        load_params.ddy = add_implicit_conversion(ctx, params->instrs, params->args[3],
                hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
        load_params.type = HLSL_RESOURCE_SAMPLE_GRAD;
    }
    else
    {
        load_params.type = HLSL_RESOURCE_SAMPLE;
        coords = add_implicit_conversion(ctx, params->instrs, params->args[1],
                hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
    }

    /* tex1D() functions never produce 1D resource declarations. For newer profiles half offset
       is used for the second coordinate, while older ones appear to replicate first coordinate.*/
    if (dim == HLSL_SAMPLER_DIM_1D)
    {
        struct hlsl_ir_var *var;
        unsigned int idx = 0;

        if (!(var = hlsl_new_synthetic_var(ctx, "coords", hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, 2), loc)))
            return false;

        initialize_var_components(ctx, params->instrs, var, &idx, coords, false);
        if (hlsl_version_ge(ctx, 4, 0))
            coords = hlsl_block_add_float_constant(ctx, params->instrs, 0.5f, loc);
        initialize_var_components(ctx, params->instrs, var, &idx, coords, false);

        coords = hlsl_block_add_simple_load(ctx, params->instrs, var, loc);
        dim = HLSL_SAMPLER_DIM_2D;
    }

    load_params.coords = coords;
    load_params.resource = params->args[0];
    load_params.format = hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, 4);
    load_params.sampling_dim = dim;

    hlsl_block_add_resource_load(ctx, params->instrs, &load_params, loc);
    return true;
}

static bool intrinsic_tex1D(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex1D", HLSL_SAMPLER_DIM_1D);
}

static bool intrinsic_tex1Dgrad(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex1Dgrad", HLSL_SAMPLER_DIM_1D);
}

static bool intrinsic_tex2D(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex2D", HLSL_SAMPLER_DIM_2D);
}

static bool intrinsic_tex2Dbias(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex2Dbias", HLSL_SAMPLER_DIM_2D);
}

static bool intrinsic_tex2Dgrad(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex2Dgrad", HLSL_SAMPLER_DIM_2D);
}

static bool intrinsic_tex2Dlod(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex2Dlod", HLSL_SAMPLER_DIM_2D);
}

static bool intrinsic_tex2Dproj(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex2Dproj", HLSL_SAMPLER_DIM_2D);
}

static bool intrinsic_tex3D(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex3D", HLSL_SAMPLER_DIM_3D);
}

static bool intrinsic_tex3Dgrad(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex3Dgrad", HLSL_SAMPLER_DIM_3D);
}

static bool intrinsic_tex3Dproj(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "tex3Dproj", HLSL_SAMPLER_DIM_3D);
}

static bool intrinsic_texCUBE(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "texCUBE", HLSL_SAMPLER_DIM_CUBE);
}

static bool intrinsic_texCUBEbias(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "texCUBEbias", HLSL_SAMPLER_DIM_CUBE);
}

static bool intrinsic_texCUBEgrad(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "texCUBEgrad", HLSL_SAMPLER_DIM_CUBE);
}

static bool intrinsic_texCUBEproj(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_tex(ctx, params, loc, "texCUBEproj", HLSL_SAMPLER_DIM_CUBE);
}

static bool intrinsic_transpose(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg = params->args[0];
    struct hlsl_type *arg_type = arg->data_type;
    struct hlsl_deref var_deref;
    struct hlsl_type *mat_type;
    struct hlsl_ir_node *load;
    struct hlsl_ir_var *var;
    unsigned int i, j;

    if (arg_type->class != HLSL_CLASS_SCALAR && arg_type->class != HLSL_CLASS_MATRIX)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, arg_type)))
            hlsl_error(ctx, &arg->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                   "Wrong type for argument 1 of transpose(): expected a matrix or scalar type, but got '%s'.",
                   string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    if (arg_type->class == HLSL_CLASS_SCALAR)
    {
        hlsl_block_add_instr(params->instrs, arg);
        return true;
    }

    mat_type = hlsl_get_matrix_type(ctx, arg_type->e.numeric.type, arg_type->e.numeric.dimy, arg_type->e.numeric.dimx);

    if (!(var = hlsl_new_synthetic_var(ctx, "transpose", mat_type, loc)))
        return false;
    hlsl_init_simple_deref_from_var(&var_deref, var);

    for (i = 0; i < arg_type->e.numeric.dimx; ++i)
    {
        for (j = 0; j < arg_type->e.numeric.dimy; ++j)
        {
            load = hlsl_add_load_component(ctx, params->instrs, arg,
                    j * arg->data_type->e.numeric.dimx + i, loc);
            hlsl_block_add_store_component(ctx, params->instrs, &var_deref,
                    i * var->data_type->e.numeric.dimx + j, load);
        }
    }

    hlsl_block_add_simple_load(ctx, params->instrs, var, loc);
    return true;
}

static bool intrinsic_trunc(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg = intrinsic_float_convert_arg(ctx, params, params->args[0], loc);

    return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_TRUNC, arg, loc);
}

static bool intrinsic_d3dcolor_to_ubyte4(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *arg = params->args[0], *ret, *c;
    struct hlsl_type *arg_type = arg->data_type;

    if (arg_type->class != HLSL_CLASS_SCALAR && !(arg_type->class == HLSL_CLASS_VECTOR
            && arg_type->e.numeric.dimx == 4))
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, arg_type)))
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Wrong argument type '%s'.", string->buffer);
            hlsl_release_string_buffer(ctx, string);
        }

        return false;
    }

    arg = intrinsic_float_convert_arg(ctx, params, arg, loc);
    c = hlsl_block_add_float_constant(ctx, params->instrs, 255.0f + (0.5f / 256.0f), loc);

    if (arg_type->class == HLSL_CLASS_VECTOR)
        arg = hlsl_block_add_swizzle(ctx, params->instrs, HLSL_SWIZZLE(Z, Y, X, W), 4, arg, loc);

    if (!(ret = add_binary_arithmetic_expr(ctx, params->instrs, HLSL_OP2_MUL, arg, c, loc)))
        return false;

    if (hlsl_version_ge(ctx, 4, 0))
        return !!add_unary_arithmetic_expr(ctx, params->instrs, HLSL_OP1_TRUNC, ret, loc);

    return true;
}

static bool intrinsic_GetRenderTargetSampleCount(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};

    if (ctx->profile->type != VKD3D_SHADER_TYPE_PIXEL || hlsl_version_lt(ctx, 4, 1))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INCOMPATIBLE_PROFILE,
                "GetRenderTargetSampleCount() can only be used from a pixel shader using version 4.1 or higher.");

    hlsl_block_add_expr(ctx, params->instrs, HLSL_OP0_RASTERIZER_SAMPLE_COUNT,
            operands, hlsl_get_vector_type(ctx, HLSL_TYPE_UINT, 1), loc);
    return true;
}

static bool intrinsic_interlocked(struct hlsl_ctx *ctx, enum hlsl_interlocked_op op,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc, const char *name)
{
    struct hlsl_ir_node *interlocked, *lhs, *coords, *val, *cmp_val = NULL, *orig_val = NULL;
    struct hlsl_type *lhs_type, *val_type;
    struct vkd3d_string_buffer *string;
    struct hlsl_deref dst_deref;

    if (hlsl_version_lt(ctx, 5, 0))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INCOMPATIBLE_PROFILE,
                "Interlocked functions can only be used in shader model 5.0 or higher.");

    if (op != HLSL_INTERLOCKED_CMP_EXCH && op != HLSL_INTERLOCKED_EXCH
            && params->args_count != 2 && params->args_count != 3)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Unexpected number of arguments to function '%s': expected 2 or 3, but got %u.",
                        name, params->args_count);
        return false;
    }

    lhs = params->args[0];
    lhs_type = lhs->data_type;

    if (op == HLSL_INTERLOCKED_CMP_EXCH)
    {
        cmp_val = params->args[1];
        val = params->args[2];
        if (params->args_count == 4)
            orig_val = params->args[3];
    }
    else
    {
        val = params->args[1];
        if (params->args_count == 3)
            orig_val = params->args[2];
    }

    if (lhs_type->class != HLSL_CLASS_SCALAR || (lhs_type->e.numeric.type != HLSL_TYPE_UINT
            && lhs_type->e.numeric.type != HLSL_TYPE_INT))
    {
        if ((string = hlsl_type_to_string(ctx, lhs_type)))
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Unexpected type for argument 0 of '%s': expected 'uint' or 'int', but got '%s'.",
                    name, string->buffer);
            hlsl_release_string_buffer(ctx, string);
        }
        return false;
    }

    /* Interlocked*() functions always take uint for the value parameters,
     * except for InterlockedMax()/InterlockedMin(). */
    if (op == HLSL_INTERLOCKED_MAX || op == HLSL_INTERLOCKED_MIN)
    {
        enum hlsl_base_type val_base_type = val->data_type->e.numeric.type;

        /* Floating values are always cast to signed integers. */
        if (val_base_type == HLSL_TYPE_FLOAT || val_base_type == HLSL_TYPE_HALF || val_base_type == HLSL_TYPE_DOUBLE)
            val_type = hlsl_get_scalar_type(ctx, HLSL_TYPE_INT);
        else
            val_type = hlsl_get_scalar_type(ctx, lhs_type->e.numeric.type);
    }
    else
    {
        val_type = hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT);
    }

    if (cmp_val && !(cmp_val = add_implicit_conversion(ctx, params->instrs, cmp_val, val_type, loc)))
        return false;
    if (!(val = add_implicit_conversion(ctx, params->instrs, val, val_type, loc)))
        return false;

    /* TODO: groupshared variables */
    if (lhs->type == HLSL_IR_INDEX && hlsl_index_chain_has_resource_access(hlsl_ir_index(lhs)))
    {
        if (!hlsl_index_is_resource_access(hlsl_ir_index(lhs)))
        {
            hlsl_fixme(ctx, &lhs->loc, "Non-direct structured resource interlocked targets.");
            return false;
        }

        if (!hlsl_init_deref_from_index_chain(ctx, &dst_deref, hlsl_ir_index(lhs)->val.node))
            return false;
        coords = hlsl_ir_index(lhs)->idx.node;

        VKD3D_ASSERT(coords->data_type->class == HLSL_CLASS_VECTOR);
        VKD3D_ASSERT(coords->data_type->e.numeric.type == HLSL_TYPE_UINT);

        if (hlsl_deref_get_type(ctx, &dst_deref)->class != HLSL_CLASS_UAV)
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Interlocked targets must be UAV or groupshared elements.");
            return false;
        }
    }
    else if (lhs->type == HLSL_IR_INDEX && hlsl_index_chain_has_tgsm_access(hlsl_ir_index(lhs)))
    {
        hlsl_fixme(ctx, loc, "Interlocked operations on indexed groupshared elements.");
        return false;
    }
    else if (lhs->type == HLSL_IR_LOAD && (hlsl_ir_load(lhs)->src.var->storage_modifiers & HLSL_STORAGE_GROUPSHARED))
    {
        hlsl_init_simple_deref_from_var(&dst_deref, hlsl_ir_load(lhs)->src.var);
        coords = hlsl_block_add_uint_constant(ctx, params->instrs, 0, loc);
    }
    else
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Interlocked targets must be UAV or groupshared elements.");
        return false;
    }

    interlocked = hlsl_new_interlocked(ctx, op, orig_val ? lhs_type : NULL, &dst_deref, coords, cmp_val, val, loc);
    hlsl_cleanup_deref(&dst_deref);
    if (!interlocked)
        return false;
    hlsl_block_add_instr(params->instrs, interlocked);

    if (orig_val)
    {
        if (orig_val->data_type->modifiers & HLSL_MODIFIER_CONST)
            hlsl_error(ctx, &orig_val->loc, VKD3D_SHADER_ERROR_HLSL_MODIFIES_CONST,
                    "Output argument to '%s' is const.", name);

        if (!add_assignment(ctx, params->instrs, orig_val, ASSIGN_OP_ASSIGN, interlocked, true))
            return false;
    }

    add_void_expr(ctx, params->instrs, loc);
    return true;
}

static bool intrinsic_InterlockedAdd(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_ADD, params, loc, "InterlockedAdd");
}

static bool intrinsic_InterlockedAnd(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_AND, params, loc, "InterlockedAnd");
}

static bool intrinsic_InterlockedCompareExchange(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_CMP_EXCH, params, loc, "InterlockedCompareExchange");
}

static bool intrinsic_InterlockedCompareStore(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_CMP_EXCH, params, loc, "InterlockedCompareStore");
}

static bool intrinsic_InterlockedExchange(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_EXCH, params, loc, "InterlockedExchange");
}

static bool intrinsic_InterlockedMax(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_MAX, params, loc, "InterlockedMax");
}

static bool intrinsic_InterlockedMin(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_MIN, params, loc, "InterlockedMin");
}

static bool intrinsic_InterlockedOr(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_OR, params, loc, "InterlockedOr");
}

static bool intrinsic_InterlockedXor(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    return intrinsic_interlocked(ctx, HLSL_INTERLOCKED_XOR, params, loc, "InterlockedXor");
}

static void validate_group_barrier_profile(struct hlsl_ctx *ctx, const struct vkd3d_shader_location *loc)
{
    if (ctx->profile->type != VKD3D_SHADER_TYPE_COMPUTE)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INCOMPATIBLE_PROFILE,
                "Group barriers can only be used in compute shaders.");
    }
}

static bool intrinsic_AllMemoryBarrier(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    validate_group_barrier_profile(ctx, loc);
    return !!hlsl_block_add_sync(ctx, params->instrs, VKD3DSSF_GLOBAL_UAV
            | VKD3DSSF_GROUP_SHARED_MEMORY, loc);
}

static bool intrinsic_AllMemoryBarrierWithGroupSync(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    validate_group_barrier_profile(ctx, loc);
    return !!hlsl_block_add_sync(ctx, params->instrs, VKD3DSSF_GLOBAL_UAV
            | VKD3DSSF_GROUP_SHARED_MEMORY | VKD3DSSF_THREAD_GROUP, loc);
}

static bool intrinsic_ConstructGSWithSO(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const char *strings[HLSL_STREAM_OUTPUT_MAX] = {0};
    size_t string_count = params->args_count - 1;
    struct hlsl_ir_compile *compile;
    struct hlsl_ir_node *node;
    uint32_t stream_index = 0;
    struct hlsl_ir_var *var;

    if (params->args_count != 2 && params->args_count != 6)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Unexpected number of arguments to ConstructGSWithSO(): expected 2 or 6, but got %u.",
                params->args_count);
        return false;
    }

    if (ctx->profile->type != VKD3D_SHADER_TYPE_EFFECT)
    {
        if (!(node = hlsl_new_compile_with_so(ctx, NULL, 0, 0, NULL, loc)))
            return false;
        hlsl_block_add_instr(params->instrs, node);
        return true;
    }

    node = params->args[0];
    switch (node->type)
    {
        case HLSL_IR_COMPILE:
            compile = hlsl_ir_compile(node);
            break;

        case HLSL_IR_LOAD:
            var = hlsl_ir_load(node)->src.var;
            if (var->data_type->class == HLSL_CLASS_ARRAY || !hlsl_type_is_shader(var->data_type))
            {
                hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "\"%s\" is not a shader compilation.", var->name);
                return false;
            }

            if (!(compile = var->default_values[0].shader))
            {
                hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "Stream output shaders can't be constructed with NULL shaders.");
                return false;
            }
            break;

        case HLSL_IR_INDEX:
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Stream output shaders can't be constructed with array indexes.");
            return false;

        default:
            hlsl_fixme(ctx, loc, "Unhandled node type in ConstructGSWithSO().");
            return false;
    }

    if (compile->profile->type != VKD3D_SHADER_TYPE_VERTEX && compile->profile->type != VKD3D_SHADER_TYPE_GEOMETRY)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Stream output shaders can only be constructed with vertex or geometry shaders.");
        return false;
    }

    if (params->args_count == 6)
    {
        struct hlsl_block stream_index_block;

        --string_count;

        if (!(node = hlsl_clone_instr(ctx, params->args[5])))
            return false;

        hlsl_block_init(&stream_index_block);
        hlsl_block_add_instr(&stream_index_block, node);

        stream_index = evaluate_static_expression_as_uint(ctx, &stream_index_block, loc);

        hlsl_block_cleanup(&stream_index_block);
    }

    for (size_t i = 0; i < string_count; ++i)
    {
        struct hlsl_ir_node *stream_node = params->args[i + 1];

        switch (stream_node->type)
        {
            case HLSL_IR_STRING_CONSTANT:
                strings[i] = hlsl_ir_string_constant(stream_node)->string;
                break;

            case HLSL_IR_CONSTANT:
                if (stream_node->data_type->class == HLSL_CLASS_NULL)
                    continue;
            /* fall-through */
            default:
                hlsl_error(ctx, &stream_node->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "Stream Output declarations must be a literal string.");
                return false;
        }
    }

    VKD3D_ASSERT(string_count <= ARRAY_SIZE(strings));

    if (!(node = hlsl_new_compile_with_so(ctx, compile, stream_index, string_count, strings, loc)))
        return false;
    hlsl_block_add_instr(params->instrs, node);

    return true;
}

static bool intrinsic_DeviceMemoryBarrier(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    if ((ctx->profile->type != VKD3D_SHADER_TYPE_COMPUTE && ctx->profile->type != VKD3D_SHADER_TYPE_PIXEL)
            || hlsl_version_lt(ctx, 4, 0))
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INCOMPATIBLE_PROFILE,
                "DeviceMemoryBarrier() can only be used in compute and pixel shaders 4.0 or higher.");
    }
    return !!hlsl_block_add_sync(ctx, params->instrs, VKD3DSSF_GLOBAL_UAV, loc);
}

static bool intrinsic_DeviceMemoryBarrierWithGroupSync(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    validate_group_barrier_profile(ctx, loc);
    return !!hlsl_block_add_sync(ctx, params->instrs, VKD3DSSF_GLOBAL_UAV
            | VKD3DSSF_THREAD_GROUP, loc);
}

static bool intrinsic_GroupMemoryBarrier(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    validate_group_barrier_profile(ctx, loc);
    return !!hlsl_block_add_sync(ctx, params->instrs,
            VKD3DSSF_GROUP_SHARED_MEMORY, loc);
}

static bool intrinsic_GroupMemoryBarrierWithGroupSync(struct hlsl_ctx *ctx,
        const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    validate_group_barrier_profile(ctx, loc);
    return !!hlsl_block_add_sync(ctx, params->instrs,
            VKD3DSSF_GROUP_SHARED_MEMORY | VKD3DSSF_THREAD_GROUP, loc);
}

static const struct intrinsic_function
{
    const char *name;
    int param_count;
    bool check_numeric;
    bool (*handler)(struct hlsl_ctx *ctx, const struct parse_initializer *params,
            const struct vkd3d_shader_location *loc);
}
intrinsic_functions[] =
{
    /* Note: these entries should be kept in alphabetical order. */
    {"AllMemoryBarrier",                    0, true,  intrinsic_AllMemoryBarrier},
    {"AllMemoryBarrierWithGroupSync",       0, true,  intrinsic_AllMemoryBarrierWithGroupSync},
    {"ConstructGSWithSO",                  -1, false, intrinsic_ConstructGSWithSO},
    {"D3DCOLORtoUBYTE4",                    1, true,  intrinsic_d3dcolor_to_ubyte4},
    {"DeviceMemoryBarrier",                 0, true,  intrinsic_DeviceMemoryBarrier},
    {"DeviceMemoryBarrierWithGroupSync",    0, true,  intrinsic_DeviceMemoryBarrierWithGroupSync},
    {"GetRenderTargetSampleCount",          0, true,  intrinsic_GetRenderTargetSampleCount},
    {"GroupMemoryBarrier",                  0, true,  intrinsic_GroupMemoryBarrier},
    {"GroupMemoryBarrierWithGroupSync",     0, true,  intrinsic_GroupMemoryBarrierWithGroupSync},
    {"InterlockedAdd",                     -1, true,  intrinsic_InterlockedAdd},
    {"InterlockedAnd",                     -1, true,  intrinsic_InterlockedAnd},
    {"InterlockedCompareExchange",          4, true,  intrinsic_InterlockedCompareExchange},
    {"InterlockedCompareStore",             3, true,  intrinsic_InterlockedCompareStore},
    {"InterlockedExchange",                 3, true,  intrinsic_InterlockedExchange},
    {"InterlockedMax",                     -1, true,  intrinsic_InterlockedMax},
    {"InterlockedMin",                     -1, true,  intrinsic_InterlockedMin},
    {"InterlockedOr",                      -1, true,  intrinsic_InterlockedOr},
    {"InterlockedXor",                     -1, true,  intrinsic_InterlockedXor},
    {"abs",                                 1, true,  intrinsic_abs},
    {"acos",                                1, true,  intrinsic_acos},
    {"all",                                 1, true,  intrinsic_all},
    {"any",                                 1, true,  intrinsic_any},
    {"asfloat",                             1, true,  intrinsic_asfloat},
    {"asin",                                1, true,  intrinsic_asin},
    {"asint",                               1, true,  intrinsic_asint},
    {"asuint",                             -1, true,  intrinsic_asuint},
    {"atan",                                1, true,  intrinsic_atan},
    {"atan2",                               2, true,  intrinsic_atan2},
    {"ceil",                                1, true,  intrinsic_ceil},
    {"clamp",                               3, true,  intrinsic_clamp},
    {"clip",                                1, true,  intrinsic_clip},
    {"cos",                                 1, true,  intrinsic_cos},
    {"cosh",                                1, true,  intrinsic_cosh},
    {"countbits",                           1, true,  intrinsic_countbits},
    {"cross",                               2, true,  intrinsic_cross},
    {"ddx",                                 1, true,  intrinsic_ddx},
    {"ddx_coarse",                          1, true,  intrinsic_ddx_coarse},
    {"ddx_fine",                            1, true,  intrinsic_ddx_fine},
    {"ddy",                                 1, true,  intrinsic_ddy},
    {"ddy_coarse",                          1, true,  intrinsic_ddy_coarse},
    {"ddy_fine",                            1, true,  intrinsic_ddy_fine},
    {"degrees",                             1, true,  intrinsic_degrees},
    {"determinant",                         1, true,  intrinsic_determinant},
    {"distance",                            2, true,  intrinsic_distance},
    {"dot",                                 2, true,  intrinsic_dot},
    {"dst",                                 2, true,  intrinsic_dst},
    {"exp",                                 1, true,  intrinsic_exp},
    {"exp2",                                1, true,  intrinsic_exp2},
    {"f16tof32",                            1, true,  intrinsic_f16tof32},
    {"f32tof16",                            1, true,  intrinsic_f32tof16},
    {"faceforward",                         3, true,  intrinsic_faceforward},
    {"firstbithigh",                        1, true,  intrinsic_firstbithigh},
    {"firstbitlow",                         1, true,  intrinsic_firstbitlow},
    {"floor",                               1, true,  intrinsic_floor},
    {"fmod",                                2, true,  intrinsic_fmod},
    {"frac",                                1, true,  intrinsic_frac},
    {"frexp",                               2, true,  intrinsic_frexp},
    {"fwidth",                              1, true,  intrinsic_fwidth},
    {"isinf",                               1, true,  intrinsic_isinf},
    {"ldexp",                               2, true,  intrinsic_ldexp},
    {"length",                              1, true,  intrinsic_length},
    {"lerp",                                3, true,  intrinsic_lerp},
    {"lit",                                 3, true,  intrinsic_lit},
    {"log",                                 1, true,  intrinsic_log},
    {"log10",                               1, true,  intrinsic_log10},
    {"log2",                                1, true,  intrinsic_log2},
    {"mad",                                 3, true,  intrinsic_mad},
    {"max",                                 2, true,  intrinsic_max},
    {"min",                                 2, true,  intrinsic_min},
    {"modf",                                2, true,  intrinsic_modf},
    {"mul",                                 2, true,  intrinsic_mul},
    {"noise",                               1, true,  intrinsic_noise},
    {"normalize",                           1, true,  intrinsic_normalize},
    {"pow",                                 2, true,  intrinsic_pow},
    {"radians",                             1, true,  intrinsic_radians},
    {"rcp",                                 1, true,  intrinsic_rcp},
    {"reflect",                             2, true,  intrinsic_reflect},
    {"refract",                             3, true,  intrinsic_refract},
    {"round",                               1, true,  intrinsic_round},
    {"rsqrt",                               1, true,  intrinsic_rsqrt},
    {"saturate",                            1, true,  intrinsic_saturate},
    {"sign",                                1, true,  intrinsic_sign},
    {"sin",                                 1, true,  intrinsic_sin},
    {"sincos",                              3, true,  intrinsic_sincos},
    {"sinh",                                1, true,  intrinsic_sinh},
    {"smoothstep",                          3, true,  intrinsic_smoothstep},
    {"sqrt",                                1, true,  intrinsic_sqrt},
    {"step",                                2, true,  intrinsic_step},
    {"tan",                                 1, true,  intrinsic_tan},
    {"tanh",                                1, true,  intrinsic_tanh},
    {"tex1D",                              -1, false, intrinsic_tex1D},
    {"tex1Dgrad",                           4, false, intrinsic_tex1Dgrad},
    {"tex2D",                              -1, false, intrinsic_tex2D},
    {"tex2Dbias",                           2, false, intrinsic_tex2Dbias},
    {"tex2Dgrad",                           4, false, intrinsic_tex2Dgrad},
    {"tex2Dlod",                            2, false, intrinsic_tex2Dlod},
    {"tex2Dproj",                           2, false, intrinsic_tex2Dproj},
    {"tex3D",                              -1, false, intrinsic_tex3D},
    {"tex3Dgrad",                           4, false, intrinsic_tex3Dgrad},
    {"tex3Dproj",                           2, false, intrinsic_tex3Dproj},
    {"texCUBE",                            -1, false, intrinsic_texCUBE},
    {"texCUBEbias",                         2, false, intrinsic_texCUBEbias},
    {"texCUBEgrad",                         4, false, intrinsic_texCUBEgrad},
    {"texCUBEproj",                         2, false, intrinsic_texCUBEproj},
    {"transpose",                           1, true,  intrinsic_transpose},
    {"trunc",                               1, true,  intrinsic_trunc},
};

static int intrinsic_function_name_compare(const void *a, const void *b)
{
    const struct intrinsic_function *func = b;

    return strcmp(a, func->name);
}

static struct hlsl_block *add_call(struct hlsl_ctx *ctx, const char *name,
        struct parse_initializer *args, const struct vkd3d_shader_location *loc)
{
    struct intrinsic_function *intrinsic;
    struct hlsl_ir_function_decl *decl;

    for (unsigned int i = 0; i < args->args_count; ++i)
    {
        if (args->args[i]->data_type->class == HLSL_CLASS_ERROR)
            goto fail;
    }

    if ((decl = find_function_call(ctx, name, args, false, loc)))
    {
        if (!add_user_call(ctx, decl, args, loc))
            goto fail;
    }
    else if ((intrinsic = bsearch(name, intrinsic_functions, ARRAY_SIZE(intrinsic_functions),
            sizeof(*intrinsic_functions), intrinsic_function_name_compare)))
    {
        if (intrinsic->param_count >= 0 && args->args_count != intrinsic->param_count)
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                    "Wrong number of arguments to function '%s': expected %u, but got %u.",
                    name, intrinsic->param_count, args->args_count);
            goto fail;
        }

        if (intrinsic->check_numeric)
        {
            unsigned int i;

            for (i = 0; i < args->args_count; ++i)
            {
                if (!hlsl_is_numeric_type(args->args[i]->data_type))
                {
                    struct vkd3d_string_buffer *string;

                    if ((string = hlsl_type_to_string(ctx, args->args[i]->data_type)))
                        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                                "Wrong type for argument %u of '%s': expected a numeric type, but got '%s'.",
                                i + 1, name, string->buffer);
                    hlsl_release_string_buffer(ctx, string);
                    goto fail;
                }
            }
        }

        if (!intrinsic->handler(ctx, args, loc))
            goto fail;
    }
    else if (rb_get(&ctx->functions, name))
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED, "No compatible %u parameter declaration for \"%s\" found.",
                args->args_count, name);
        goto fail;
    }
    else
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED, "Function \"%s\" is not defined.", name);
        goto fail;
    }
    vkd3d_free(args->args);
    return args->instrs;

fail:
    args->instrs->value = ctx->error_instr;
    vkd3d_free(args->args);
    return args->instrs;
}

static struct hlsl_block *add_shader_compilation(struct hlsl_ctx *ctx, const char *profile_name,
        const char *function_name, struct parse_initializer *args, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_profile_info *profile_info;
    struct hlsl_ir_function_decl *decl;
    struct hlsl_block *block = NULL;
    struct hlsl_ir_node *compile;

    if (!ctx->in_state_block && ctx->cur_scope != ctx->globals)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_MISPLACED_COMPILE,
                "Shader compilation statements must be in global scope or a state block.");
        goto out;
    }

    if (!(decl = find_function_call(ctx, function_name, args, true, loc)))
    {
        if (rb_get(&ctx->functions, function_name))
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED,
                    "No compatible \"%s\" declaration with %u uniform parameters found.",
                    function_name, args->args_count);
        }
        else
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED,
                    "Function \"%s\" is not defined.", function_name);
        }
        goto out;
    }

    if (!(profile_info = hlsl_get_target_info(profile_name)))
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_PROFILE, "Unknown profile \"%s\".", profile_name);
        goto out;
    }

    for (unsigned int i = 0; i < args->args_count; ++i)
    {
        if (args->args[i]->data_type->class == HLSL_CLASS_ERROR)
        {
            args->instrs->value = ctx->error_instr;
            free(args->args);
            return args->instrs;
        }
    }

    if (!parse_function_call_arguments(ctx, decl, args, true, loc))
        goto out;

    if (!(compile = hlsl_new_compile(ctx, profile_info, decl, args->instrs, loc)))
        goto out;

    block = make_block(ctx, compile);

out:
    free_parse_initializer(args);
    return block;
}

static struct hlsl_block *add_constructor(struct hlsl_ctx *ctx, struct hlsl_type *type,
        struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_var *var;

    if (!hlsl_is_numeric_type(type))
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Constructor data type %s is not numeric.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return NULL;
    }

    if (!(var = hlsl_new_synthetic_var(ctx, "constructor", type, loc)))
        return NULL;

    initialize_var(ctx, var, params, false);

    hlsl_block_add_simple_load(ctx, params->instrs, var, loc);

    vkd3d_free(params->args);
    return params->instrs;
}

static bool add_ternary(struct hlsl_ctx *ctx, struct hlsl_block *block,
        struct hlsl_ir_node *cond, struct hlsl_ir_node *first, struct hlsl_ir_node *second)
{
    struct hlsl_ir_node *args[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_type *cond_type = cond->data_type;
    struct hlsl_type *common_type;

    if (cond->data_type->class == HLSL_CLASS_ERROR
            || first->data_type->class == HLSL_CLASS_ERROR
            || second->data_type->class == HLSL_CLASS_ERROR)
    {
        block->value = ctx->error_instr;
        return true;
    }

    if (cond_type->class > HLSL_CLASS_LAST_NUMERIC)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, cond_type)))
            hlsl_error(ctx, &cond->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Ternary condition type '%s' is not numeric.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    if (first->data_type->class <= HLSL_CLASS_LAST_NUMERIC
            && second->data_type->class <= HLSL_CLASS_LAST_NUMERIC)
    {
        if (!(common_type = get_common_numeric_type(ctx, first, second, &first->loc)))
            return false;

        if (cond_type->e.numeric.dimx == 1 && cond_type->e.numeric.dimy == 1)
        {
            cond_type = hlsl_get_numeric_type(ctx, common_type->class,
                    HLSL_TYPE_BOOL, common_type->e.numeric.dimx, common_type->e.numeric.dimy);
            cond = add_implicit_conversion(ctx, block, cond, cond_type, &cond->loc);
        }
        else
        {
            if (common_type->e.numeric.dimx == 1 && common_type->e.numeric.dimy == 1)
            {
                common_type = hlsl_get_numeric_type(ctx, cond_type->class,
                        common_type->e.numeric.type, cond_type->e.numeric.dimx, cond_type->e.numeric.dimy);
            }
            else if (cond_type->e.numeric.dimx != common_type->e.numeric.dimx
                    || cond_type->e.numeric.dimy != common_type->e.numeric.dimy)
            {
                /* This condition looks wrong but is correct.
                * floatN is compatible with float1xN, but not with floatNx1. */

                struct vkd3d_string_buffer *cond_string, *value_string;

                cond_string = hlsl_type_to_string(ctx, cond_type);
                value_string = hlsl_type_to_string(ctx, common_type);
                if (cond_string && value_string)
                    hlsl_error(ctx, &first->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Ternary condition type '%s' is not compatible with value type '%s'.",
                            cond_string->buffer, value_string->buffer);
                hlsl_release_string_buffer(ctx, cond_string);
                hlsl_release_string_buffer(ctx, value_string);
            }

            cond_type = hlsl_get_numeric_type(ctx, common_type->class, HLSL_TYPE_BOOL,
                    common_type->e.numeric.dimx, common_type->e.numeric.dimy);
            cond = add_implicit_conversion(ctx, block, cond, cond_type, &cond->loc);
        }

        first = add_implicit_conversion(ctx, block, first, common_type, &first->loc);
        second = add_implicit_conversion(ctx, block, second, common_type, &second->loc);
    }
    else
    {
        struct vkd3d_string_buffer *first_string, *second_string;

        if (!hlsl_types_are_equal(first->data_type, second->data_type))
        {
            first_string = hlsl_type_to_string(ctx, first->data_type);
            second_string = hlsl_type_to_string(ctx, second->data_type);
            if (first_string && second_string)
                hlsl_error(ctx, &first->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "Ternary argument types '%s' and '%s' do not match.",
                        first_string->buffer, second_string->buffer);
            hlsl_release_string_buffer(ctx, first_string);
            hlsl_release_string_buffer(ctx, second_string);
        }

        cond_type = hlsl_get_numeric_type(ctx, cond_type->class, HLSL_TYPE_BOOL,
                cond_type->e.numeric.dimx, cond_type->e.numeric.dimy);
        cond = add_implicit_conversion(ctx, block, cond, cond_type, &cond->loc);
        common_type = first->data_type;
    }

    VKD3D_ASSERT(cond->data_type->e.numeric.type == HLSL_TYPE_BOOL);

    args[0] = cond;
    args[1] = first;
    args[2] = second;
    return add_expr(ctx, block, HLSL_OP3_TERNARY, args, common_type, &first->loc);
}

static unsigned int hlsl_offset_dim_count(enum hlsl_sampler_dim dim)
{
    switch (dim)
    {
        case HLSL_SAMPLER_DIM_1D:
        case HLSL_SAMPLER_DIM_1DARRAY:
            return 1;
        case HLSL_SAMPLER_DIM_2D:
        case HLSL_SAMPLER_DIM_2DMS:
        case HLSL_SAMPLER_DIM_2DARRAY:
        case HLSL_SAMPLER_DIM_2DMSARRAY:
            return 2;
        case HLSL_SAMPLER_DIM_3D:
            return 3;
        case HLSL_SAMPLER_DIM_CUBE:
        case HLSL_SAMPLER_DIM_CUBEARRAY:
        case HLSL_SAMPLER_DIM_BUFFER:
        case HLSL_SAMPLER_DIM_RAW_BUFFER:
        case HLSL_SAMPLER_DIM_STRUCTURED_BUFFER:
            /* Offset parameters not supported for these types. */
            return 0;
        default:
            vkd3d_unreachable();
    }
}

static bool raise_invalid_method_object_type(struct hlsl_ctx *ctx, const struct hlsl_type *object_type,
        const char *method, const struct vkd3d_shader_location *loc)
{
    struct vkd3d_string_buffer *string;

    if ((string = hlsl_type_to_string(ctx, object_type)))
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED,
                "Method '%s' is not defined on type '%s'.", method, string->buffer);
    hlsl_release_string_buffer(ctx, string);
    return false;
}

static bool add_raw_load_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_resource_load_params load_params = {.type = HLSL_RESOURCE_LOAD};
    unsigned int value_dim;

    if (params->args_count != 1 && params->args_count != 2)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method 'Load': expected between 1 and 2, but got %u.",
                params->args_count);
        return false;
    }

    if (params->args_count == 2)
    {
        hlsl_fixme(ctx, loc, "Tiled resource status argument.");
        return false;
    }

    if (params->args[0]->data_type->class != HLSL_CLASS_SCALAR)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Scalar address argument expected for '%s'.", name);
        return false;
    }

    if (!strcmp(name, "Load"))
        value_dim = 1;
    else if (!strcmp(name, "Load2"))
        value_dim = 2;
    else if (!strcmp(name, "Load3"))
        value_dim = 3;
    else
        value_dim = 4;

    load_params.coords = add_implicit_conversion(ctx, block, params->args[0],
            hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), loc);
    load_params.format = hlsl_get_vector_type(ctx, HLSL_TYPE_UINT, value_dim);
    load_params.resource = object;
    hlsl_block_add_resource_load(ctx, block, &load_params, loc);
    return true;
}

static bool add_load_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    struct hlsl_resource_load_params load_params = {.type = HLSL_RESOURCE_LOAD};
    unsigned int sampler_dim, offset_dim;
    bool multisampled;

    if (object_type->sampler_dim == HLSL_SAMPLER_DIM_RAW_BUFFER)
        return add_raw_load_method_call(ctx, block, object, name, params, loc);

    if (object_type->sampler_dim == HLSL_SAMPLER_DIM_STRUCTURED_BUFFER)
    {
        hlsl_fixme(ctx, loc, "Method '%s' for structured buffers.", name);
        return false;
    }

    sampler_dim = hlsl_sampler_dim_count(object_type->sampler_dim);
    offset_dim = hlsl_offset_dim_count(object_type->sampler_dim);

    multisampled = object_type->sampler_dim == HLSL_SAMPLER_DIM_2DMS
            || object_type->sampler_dim == HLSL_SAMPLER_DIM_2DMSARRAY;

    if (params->args_count < 1 + multisampled || params->args_count > 2 + multisampled + !!offset_dim)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method 'Load': expected between %u and %u, but got %u.",
                1 + multisampled, 2 + multisampled + !!offset_dim, params->args_count);
        return false;
    }

    if (multisampled)
        load_params.sample_index = add_implicit_conversion(ctx, block, params->args[1],
                hlsl_get_scalar_type(ctx, HLSL_TYPE_INT), loc);

    if (!!offset_dim && params->args_count > 1 + multisampled)
        load_params.texel_offset = add_implicit_conversion(ctx, block, params->args[1 + multisampled],
                hlsl_get_vector_type(ctx, HLSL_TYPE_INT, offset_dim), loc);

    if (params->args_count > 1 + multisampled + !!offset_dim)
    {
        hlsl_fixme(ctx, loc, "Tiled resource status argument.");
    }

    /* +1 for the mipmap level for non-multisampled textures */
    load_params.coords = add_implicit_conversion(ctx, block, params->args[0],
            hlsl_get_vector_type(ctx, HLSL_TYPE_INT, sampler_dim + !multisampled), loc);
    load_params.format = object_type->e.resource.format;
    load_params.resource = object;
    hlsl_block_add_resource_load(ctx, block, &load_params, loc);
    return true;
}

static bool add_sample_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    struct hlsl_resource_load_params load_params = {.type = HLSL_RESOURCE_SAMPLE};
    unsigned int sampler_dim, offset_dim;
    const struct hlsl_type *sampler_type;

    sampler_dim = hlsl_sampler_dim_count(object_type->sampler_dim);
    offset_dim = hlsl_offset_dim_count(object_type->sampler_dim);

    if (params->args_count < 2 || params->args_count > 4 + !!offset_dim)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method 'Sample': expected from 2 to %u, but got %u.",
                4 + !!offset_dim, params->args_count);
        return false;
    }

    sampler_type = params->args[0]->data_type;
    if (sampler_type->class != HLSL_CLASS_SAMPLER || sampler_type->sampler_dim != HLSL_SAMPLER_DIM_GENERIC)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, sampler_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong type for argument 0 of Sample(): expected 'sampler', but got '%s'.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    load_params.coords = add_implicit_conversion(ctx, block, params->args[1],
            hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);

    if (offset_dim && params->args_count > 2)
        load_params.texel_offset = add_implicit_conversion(ctx, block, params->args[2],
                hlsl_get_vector_type(ctx, HLSL_TYPE_INT, offset_dim), loc);

    if (params->args_count > 2 + !!offset_dim)
        hlsl_fixme(ctx, loc, "Sample() clamp parameter.");
    if (params->args_count > 3 + !!offset_dim)
        hlsl_fixme(ctx, loc, "Tiled resource status argument.");

    load_params.format = object_type->e.resource.format;
    load_params.resource = object;
    load_params.sampler = params->args[0];
    hlsl_block_add_resource_load(ctx, block, &load_params, loc);
    return true;
}

static bool add_sample_cmp_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    struct hlsl_resource_load_params load_params = { 0 };
    unsigned int sampler_dim, offset_dim;
    const struct hlsl_type *sampler_type;

    sampler_dim = hlsl_sampler_dim_count(object_type->sampler_dim);
    offset_dim = hlsl_offset_dim_count(object_type->sampler_dim);

    if (!strcmp(name, "SampleCmpLevelZero"))
        load_params.type = HLSL_RESOURCE_SAMPLE_CMP_LZ;
    else
        load_params.type = HLSL_RESOURCE_SAMPLE_CMP;

    if (params->args_count < 3 || params->args_count > 5 + !!offset_dim)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method '%s': expected from 3 to %u, but got %u.",
                name, 5 + !!offset_dim, params->args_count);
        return false;
    }

    sampler_type = params->args[0]->data_type;
    if (sampler_type->class != HLSL_CLASS_SAMPLER || sampler_type->sampler_dim != HLSL_SAMPLER_DIM_COMPARISON)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, sampler_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong type for argument 0 of %s(): expected 'SamplerComparisonState', but got '%s'.",
                    name, string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    load_params.coords = add_implicit_conversion(ctx, block, params->args[1],
            hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
    load_params.cmp = add_implicit_conversion(ctx, block, params->args[2],
            hlsl_get_scalar_type(ctx, HLSL_TYPE_FLOAT), loc);

    if (offset_dim && params->args_count > 3)
        load_params.texel_offset = add_implicit_conversion(ctx, block, params->args[2],
                hlsl_get_vector_type(ctx, HLSL_TYPE_INT, offset_dim), loc);

    if (params->args_count > 3 + !!offset_dim)
        hlsl_fixme(ctx, loc, "%s() clamp parameter.", name);
    if (params->args_count > 4 + !!offset_dim)
        hlsl_fixme(ctx, loc, "Tiled resource status argument.");

    load_params.format = object_type->e.resource.format;
    load_params.resource = object;
    load_params.sampler = params->args[0];
    hlsl_block_add_resource_load(ctx, block, &load_params, loc);
    return true;
}

static bool add_gather_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    struct hlsl_resource_load_params load_params = {0};
    unsigned int sampler_dim, offset_dim;
    const struct hlsl_type *sampler_type;
    unsigned int read_channel;

    sampler_dim = hlsl_sampler_dim_count(object_type->sampler_dim);
    offset_dim = hlsl_offset_dim_count(object_type->sampler_dim);

    if (!strcmp(name, "GatherGreen"))
    {
        load_params.type = HLSL_RESOURCE_GATHER_GREEN;
        read_channel = 1;
    }
    else if (!strcmp(name, "GatherBlue"))
    {
        load_params.type = HLSL_RESOURCE_GATHER_BLUE;
        read_channel = 2;
    }
    else if (!strcmp(name, "GatherAlpha"))
    {
        load_params.type = HLSL_RESOURCE_GATHER_ALPHA;
        read_channel = 3;
    }
    else
    {
        load_params.type = HLSL_RESOURCE_GATHER_RED;
        read_channel = 0;
    }

    if (!strcmp(name, "Gather") || !offset_dim)
    {
        if (params->args_count < 2 || params->args_count > 3 + !!offset_dim)
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                    "Wrong number of arguments to method '%s': expected from 2 to %u, but got %u.",
                    name, 3 + !!offset_dim, params->args_count);
            return false;
        }
    }
    else if (params->args_count < 2 || params->args_count == 5 || params->args_count > 7)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method '%s': expected 2, 3, 4, 6 or 7, but got %u.",
                name, params->args_count);
        return false;
    }

    if (params->args_count == 3 + !!offset_dim || params->args_count == 7)
        hlsl_fixme(ctx, loc, "Tiled resource status argument.");

    if (params->args_count == 6 || params->args_count == 7)
    {
        hlsl_fixme(ctx, loc, "Multiple %s() offset parameters.", name);
    }
    else if (offset_dim && params->args_count > 2)
    {
        load_params.texel_offset = add_implicit_conversion(ctx, block, params->args[2],
                hlsl_get_vector_type(ctx, HLSL_TYPE_INT, offset_dim), loc);
    }

    sampler_type = params->args[0]->data_type;
    if (sampler_type->class != HLSL_CLASS_SAMPLER || sampler_type->sampler_dim != HLSL_SAMPLER_DIM_GENERIC)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, sampler_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong type for argument 1 of %s(): expected 'sampler', but got '%s'.", name, string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    if (read_channel >= object_type->e.resource.format->e.numeric.dimx)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Method %s() requires at least %u channels.", name, read_channel + 1);
        return false;
    }

    load_params.coords = add_implicit_conversion(ctx, block, params->args[1],
            hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
    load_params.format = hlsl_get_vector_type(ctx, object_type->e.resource.format->e.numeric.type, 4);
    load_params.resource = object;
    load_params.sampler = params->args[0];
    hlsl_block_add_resource_load(ctx, block, &load_params, loc);
    return true;
}

static bool add_gather_cmp_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    struct hlsl_resource_load_params load_params = {0};
    unsigned int sampler_dim, offset_dim;
    const struct hlsl_type *sampler_type;

    sampler_dim = hlsl_sampler_dim_count(object_type->sampler_dim);
    offset_dim = hlsl_offset_dim_count(object_type->sampler_dim);

    if (!strcmp(name, "GatherCmpGreen"))
        load_params.type = HLSL_RESOURCE_GATHER_CMP_GREEN;
    else if (!strcmp(name, "GatherCmpBlue"))
        load_params.type = HLSL_RESOURCE_GATHER_CMP_BLUE;
    else if (!strcmp(name, "GatherCmpAlpha"))
        load_params.type = HLSL_RESOURCE_GATHER_CMP_ALPHA;
    else
        load_params.type = HLSL_RESOURCE_GATHER_CMP_RED;

    if (!strcmp(name, "GatherCmp") || !offset_dim)
    {
        if (params->args_count < 3 || params->args_count > 4 + !!offset_dim)
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                    "Wrong number of arguments to method '%s': expected from 3 to %u, but got %u.",
                    name, 4 + !!offset_dim, params->args_count);
            return false;
        }
    }
    else if (params->args_count < 3 || params->args_count == 6 || params->args_count > 8)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method '%s': expected 3, 4, 5, 7, or 8, but got %u.",
                name, params->args_count);
        return false;
    }

    if (params->args_count == 5 || params->args_count == 8)
    {
        hlsl_fixme(ctx, loc, "Tiled resource status argument.");
    }
    else if (offset_dim && params->args_count > 3)
    {
        if (!(load_params.texel_offset = add_implicit_conversion(ctx, block, params->args[3],
                hlsl_get_vector_type(ctx, HLSL_TYPE_INT, offset_dim), loc)))
            return false;
    }

    sampler_type = params->args[0]->data_type;
    if (sampler_type->class != HLSL_CLASS_SAMPLER || sampler_type->sampler_dim != HLSL_SAMPLER_DIM_COMPARISON)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, sampler_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong type for argument 0 of %s(): expected 'SamplerComparisonState', but got '%s'.",
                    name, string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    if (!(load_params.coords = add_implicit_conversion(ctx, block, params->args[1],
            hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc)))
        return false;

    if (!(load_params.cmp = add_implicit_conversion(ctx, block, params->args[2],
            hlsl_get_scalar_type(ctx, HLSL_TYPE_FLOAT), loc)))
        return false;

    load_params.format = hlsl_get_vector_type(ctx, object_type->e.resource.format->e.numeric.type, 4);
    load_params.resource = object;
    load_params.sampler = params->args[0];
    hlsl_block_add_resource_load(ctx, block, &load_params, loc);
    return true;
}

static bool add_assignment_from_component(struct hlsl_ctx *ctx, struct hlsl_block *instrs, struct hlsl_ir_node *dest,
        struct hlsl_ir_node *src, unsigned int component, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *load;

    if (!dest)
        return true;

    load = hlsl_add_load_component(ctx, instrs, src, component, loc);
    if (!add_assignment(ctx, instrs, dest, ASSIGN_OP_ASSIGN, load, false))
        return false;

    return true;
}

static bool add_getdimensions_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    bool uint_resinfo, has_uint_arg, has_float_arg;
    struct hlsl_resource_load_params load_params;
    struct hlsl_ir_node *sample_info, *res_info;
    struct hlsl_type *uint_type, *float_type;
    unsigned int i, j;
    enum func_argument
    {
        ARG_MIP_LEVEL,
        ARG_WIDTH,
        ARG_HEIGHT,
        ARG_ELEMENT_COUNT,
        ARG_LEVEL_COUNT,
        ARG_SAMPLE_COUNT,
        ARG_MAX_ARGS,
    };
    struct hlsl_ir_node *args[ARG_MAX_ARGS] = { 0 };
    static const struct overload
    {
        enum hlsl_sampler_dim sampler_dim;
        unsigned int args_count;
        enum func_argument args[ARG_MAX_ARGS];
    }
    overloads[] =
    {
        { HLSL_SAMPLER_DIM_1D, 1, { ARG_WIDTH } },
        { HLSL_SAMPLER_DIM_1D, 3, { ARG_MIP_LEVEL, ARG_WIDTH, ARG_LEVEL_COUNT } },
        { HLSL_SAMPLER_DIM_1DARRAY, 2, { ARG_WIDTH, ARG_ELEMENT_COUNT } },
        { HLSL_SAMPLER_DIM_1DARRAY, 4, { ARG_MIP_LEVEL, ARG_WIDTH, ARG_ELEMENT_COUNT, ARG_LEVEL_COUNT } },
        { HLSL_SAMPLER_DIM_2D, 2, { ARG_WIDTH, ARG_HEIGHT } },
        { HLSL_SAMPLER_DIM_2D, 4, { ARG_MIP_LEVEL, ARG_WIDTH, ARG_HEIGHT, ARG_LEVEL_COUNT } },
        { HLSL_SAMPLER_DIM_2DARRAY, 3, { ARG_WIDTH, ARG_HEIGHT, ARG_ELEMENT_COUNT } },
        { HLSL_SAMPLER_DIM_2DARRAY, 5, { ARG_MIP_LEVEL, ARG_WIDTH, ARG_HEIGHT, ARG_ELEMENT_COUNT, ARG_LEVEL_COUNT } },
        { HLSL_SAMPLER_DIM_3D, 3, { ARG_WIDTH, ARG_HEIGHT, ARG_ELEMENT_COUNT } },
        { HLSL_SAMPLER_DIM_3D, 5, { ARG_MIP_LEVEL, ARG_WIDTH, ARG_HEIGHT, ARG_ELEMENT_COUNT, ARG_LEVEL_COUNT } },
        { HLSL_SAMPLER_DIM_CUBE, 2, { ARG_WIDTH, ARG_HEIGHT } },
        { HLSL_SAMPLER_DIM_CUBE, 4, { ARG_MIP_LEVEL, ARG_WIDTH, ARG_HEIGHT, ARG_LEVEL_COUNT } },
        { HLSL_SAMPLER_DIM_CUBEARRAY, 3, { ARG_WIDTH, ARG_HEIGHT, ARG_ELEMENT_COUNT } },
        { HLSL_SAMPLER_DIM_CUBEARRAY, 5, { ARG_MIP_LEVEL, ARG_WIDTH, ARG_HEIGHT, ARG_ELEMENT_COUNT, ARG_LEVEL_COUNT } },
        { HLSL_SAMPLER_DIM_2DMS, 3, { ARG_WIDTH, ARG_HEIGHT, ARG_SAMPLE_COUNT } },
        { HLSL_SAMPLER_DIM_2DMSARRAY, 4, { ARG_WIDTH, ARG_HEIGHT, ARG_ELEMENT_COUNT, ARG_SAMPLE_COUNT } },
        { HLSL_SAMPLER_DIM_BUFFER, 1, { ARG_WIDTH} },
    };
    const struct overload *o = NULL;

    if (object_type->sampler_dim == HLSL_SAMPLER_DIM_STRUCTURED_BUFFER)
    {
        hlsl_fixme(ctx, loc, "Method '%s' for structured buffers.", name);
        return false;
    }

    uint_type = hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT);
    float_type = hlsl_get_scalar_type(ctx, HLSL_TYPE_FLOAT);
    has_uint_arg = has_float_arg = false;
    for (i = 0; i < ARRAY_SIZE(overloads); ++i)
    {
        const struct overload *iter = &overloads[i];

        if (iter->sampler_dim == object_type->sampler_dim && iter->args_count == params->args_count)
        {
            for (j = 0; j < params->args_count; ++j)
            {
                args[iter->args[j]] = params->args[j];

                /* Input parameter. */
                if (iter->args[j] == ARG_MIP_LEVEL)
                {
                    args[ARG_MIP_LEVEL] = add_implicit_conversion(ctx, block, args[ARG_MIP_LEVEL],
                            hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), loc);
                    continue;
                }

                has_float_arg |= hlsl_types_are_equal(params->args[j]->data_type, float_type);
                has_uint_arg |= hlsl_types_are_equal(params->args[j]->data_type, uint_type);

                if (params->args[j]->data_type->class != HLSL_CLASS_SCALAR)
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Expected scalar arguments.");
                    break;
                }
            }
            o = iter;
            break;
        }
    }
    uint_resinfo = !has_float_arg && has_uint_arg;

    if (!o)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, object_type)))
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                    "Unexpected number of arguments %u for %s.%s().", params->args_count, string->buffer, name);
            hlsl_release_string_buffer(ctx, string);
        }
    }

    if (!args[ARG_MIP_LEVEL])
        args[ARG_MIP_LEVEL] = hlsl_block_add_uint_constant(ctx, block, 0, loc);

    memset(&load_params, 0, sizeof(load_params));
    load_params.type = HLSL_RESOURCE_RESINFO;
    load_params.resource = object;
    load_params.lod = args[ARG_MIP_LEVEL];
    load_params.format = hlsl_get_vector_type(ctx, uint_resinfo ? HLSL_TYPE_UINT : HLSL_TYPE_FLOAT, 4);
    res_info = hlsl_block_add_resource_load(ctx, block, &load_params, loc);

    if (!add_assignment_from_component(ctx, block, args[ARG_WIDTH], res_info, 0, loc))
        return false;

    if (!add_assignment_from_component(ctx, block, args[ARG_HEIGHT], res_info, 1, loc))
        return false;

    if (!add_assignment_from_component(ctx, block, args[ARG_ELEMENT_COUNT], res_info,
            object_type->sampler_dim == HLSL_SAMPLER_DIM_1DARRAY ? 1 : 2, loc))
    {
        return false;
    }

    if (!add_assignment_from_component(ctx, block, args[ARG_LEVEL_COUNT], res_info, 3, loc))
        return false;

    if (args[ARG_SAMPLE_COUNT])
    {
        memset(&load_params, 0, sizeof(load_params));
        load_params.type = HLSL_RESOURCE_SAMPLE_INFO;
        load_params.resource = object;
        load_params.format = args[ARG_SAMPLE_COUNT]->data_type;
        sample_info = hlsl_block_add_resource_load(ctx, block, &load_params, loc);

        if (!add_assignment(ctx, block, args[ARG_SAMPLE_COUNT], ASSIGN_OP_ASSIGN, sample_info, false))
            return false;
    }

    add_void_expr(ctx, block, loc);
    return true;
}

static bool add_sample_lod_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    struct hlsl_resource_load_params load_params = { 0 };
    unsigned int sampler_dim, offset_dim;
    const struct hlsl_type *sampler_type;

    sampler_dim = hlsl_sampler_dim_count(object_type->sampler_dim);
    offset_dim = hlsl_offset_dim_count(object_type->sampler_dim);

    if (!strcmp(name, "SampleLevel"))
        load_params.type = HLSL_RESOURCE_SAMPLE_LOD;
    else
        load_params.type = HLSL_RESOURCE_SAMPLE_LOD_BIAS;

    if (params->args_count < 3 || params->args_count > 4 + !!offset_dim)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method '%s': expected from 3 to %u, but got %u.",
                name, 4 + !!offset_dim, params->args_count);
        return false;
    }

    sampler_type = params->args[0]->data_type;
    if (sampler_type->class != HLSL_CLASS_SAMPLER || sampler_type->sampler_dim != HLSL_SAMPLER_DIM_GENERIC)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, sampler_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong type for argument 0 of %s(): expected 'sampler', but got '%s'.", name, string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    load_params.coords = add_implicit_conversion(ctx, block, params->args[1],
            hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
    load_params.lod = add_implicit_conversion(ctx, block, params->args[2],
            hlsl_get_scalar_type(ctx, HLSL_TYPE_FLOAT), loc);

    if (offset_dim && params->args_count > 3)
        load_params.texel_offset = add_implicit_conversion(ctx, block, params->args[3],
                hlsl_get_vector_type(ctx, HLSL_TYPE_INT, offset_dim), loc);

    if (params->args_count > 3 + !!offset_dim)
        hlsl_fixme(ctx, loc, "Tiled resource status argument.");

    load_params.format = object_type->e.resource.format;
    load_params.resource = object;
    load_params.sampler = params->args[0];
    hlsl_block_add_resource_load(ctx, block, &load_params, loc);
    return true;
}

static bool add_sample_grad_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    struct hlsl_resource_load_params load_params = { 0 };
    unsigned int sampler_dim, offset_dim;
    const struct hlsl_type *sampler_type;

    sampler_dim = hlsl_sampler_dim_count(object_type->sampler_dim);
    offset_dim = hlsl_offset_dim_count(object_type->sampler_dim);

    load_params.type = HLSL_RESOURCE_SAMPLE_GRAD;

    if (params->args_count < 4 || params->args_count > 5 + !!offset_dim)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method '%s': expected from 4 to %u, but got %u.",
                name, 5 + !!offset_dim, params->args_count);
        return false;
    }

    sampler_type = params->args[0]->data_type;
    if (sampler_type->class != HLSL_CLASS_SAMPLER || sampler_type->sampler_dim != HLSL_SAMPLER_DIM_GENERIC)
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, sampler_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Wrong type for argument 0 of %s(): expected 'sampler', but got '%s'.", name, string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    load_params.coords = add_implicit_conversion(ctx, block, params->args[1],
            hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
    load_params.ddx = add_implicit_conversion(ctx, block, params->args[2],
            hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);
    load_params.ddy = add_implicit_conversion(ctx, block, params->args[3],
            hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, sampler_dim), loc);

    if (offset_dim && params->args_count > 4)
        load_params.texel_offset = add_implicit_conversion(ctx, block, params->args[4],
                hlsl_get_vector_type(ctx, HLSL_TYPE_INT, offset_dim), loc);

    if (params->args_count > 4 + !!offset_dim)
        hlsl_fixme(ctx, loc, "Tiled resource status argument.");

    load_params.format = object_type->e.resource.format;
    load_params.resource = object;
    load_params.sampler = params->args[0];
    hlsl_block_add_resource_load(ctx, block, &load_params, loc);
    return true;
}

static bool add_store_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *offset, *rhs;
    struct hlsl_deref resource_deref;
    unsigned int value_dim;
    uint32_t writemask;

    if (params->args_count != 2)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method '%s': expected 2.", name);
        return false;
    }

    if (!strcmp(name, "Store"))
        value_dim = 1;
    else if (!strcmp(name, "Store2"))
        value_dim = 2;
    else if (!strcmp(name, "Store3"))
        value_dim = 3;
    else
        value_dim = 4;

    offset = add_implicit_conversion(ctx, block, params->args[0],
            hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), loc);
    rhs = add_implicit_conversion(ctx, block, params->args[1],
            hlsl_get_vector_type(ctx, HLSL_TYPE_UINT, value_dim), loc);
    writemask = vkd3d_write_mask_from_component_count(value_dim);

    if (!hlsl_init_deref_from_index_chain(ctx, &resource_deref, object))
        return false;

    hlsl_block_add_resource_store(ctx, block, HLSL_RESOURCE_STORE, &resource_deref, offset, rhs, writemask, loc);
    hlsl_cleanup_deref(&resource_deref);

    return true;
}

static bool add_so_append_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_deref so_deref;
    struct hlsl_ir_node *rhs;

    if (params->args_count != 1)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method '%s': expected 1.", name);
        return false;
    }

    if (!hlsl_init_deref_from_index_chain(ctx, &so_deref, object))
        return false;

    if (!(rhs = add_implicit_conversion(ctx, block, params->args[0], object->data_type->e.so.type, loc)))
        return false;

    hlsl_block_add_resource_store(ctx, block, HLSL_RESOURCE_STREAM_APPEND, &so_deref, NULL, rhs, 0, loc);
    hlsl_cleanup_deref(&so_deref);

    return true;
}

static bool add_so_restartstrip_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    struct hlsl_deref so_deref;

    if (params->args_count)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_WRONG_PARAMETER_COUNT,
                "Wrong number of arguments to method '%s': expected 0.", name);
        return false;
    }

    if (!hlsl_init_deref_from_index_chain(ctx, &so_deref, object))
        return false;

    hlsl_block_add_resource_store(ctx, block, HLSL_RESOURCE_STREAM_RESTART, &so_deref, NULL, NULL, 0, loc);
    hlsl_cleanup_deref(&so_deref);

    return true;
}

static const struct method_function
{
    const char *name;
    bool (*handler)(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
            const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc);
    char valid_dims[HLSL_SAMPLER_DIM_MAX + 1];
}
texture_methods[] =
{
    { "Gather",             add_gather_method_call,        "00010101001000" },
    { "GatherAlpha",        add_gather_method_call,        "00010101001000" },
    { "GatherBlue",         add_gather_method_call,        "00010101001000" },
    { "GatherCmp",          add_gather_cmp_method_call,    "00010101001000" },
    { "GatherCmpAlpha",     add_gather_cmp_method_call,    "00010101001000" },
    { "GatherCmpBlue",      add_gather_cmp_method_call,    "00010101001000" },
    { "GatherCmpGreen",     add_gather_cmp_method_call,    "00010101001000" },
    { "GatherCmpRed",       add_gather_cmp_method_call,    "00010101001000" },
    { "GatherGreen",        add_gather_method_call,        "00010101001000" },
    { "GatherRed",          add_gather_method_call,        "00010101001000" },

    { "GetDimensions",      add_getdimensions_method_call, "00111111111110" },

    { "Load",               add_load_method_call,          "00111011110111" },
    { "Load2",              add_raw_load_method_call,      "00000000000001" },
    { "Load3",              add_raw_load_method_call,      "00000000000001" },
    { "Load4",              add_raw_load_method_call,      "00000000000001" },

    { "Sample",             add_sample_method_call,        "00111111001000" },
    { "SampleBias",         add_sample_lod_method_call,    "00111111001000" },
    { "SampleCmp",          add_sample_cmp_method_call,    "00111111001000" },
    { "SampleCmpLevelZero", add_sample_cmp_method_call,    "00111111001000" },
    { "SampleGrad",         add_sample_grad_method_call,   "00111111001000" },
    { "SampleLevel",        add_sample_lod_method_call,    "00111111001000" },
};

static const struct method_function uav_methods[] =
{
    { "Store",  add_store_method_call, "00000000000001" },
    { "Store2", add_store_method_call, "00000000000001" },
    { "Store3", add_store_method_call, "00000000000001" },
    { "Store4", add_store_method_call, "00000000000001" },
};

static const struct method_function so_methods[] =
{
    { "Append",       add_so_append_method_call,       "" },
    { "RestartStrip", add_so_restartstrip_method_call, "" },
};

static int object_method_function_name_compare(const void *a, const void *b)
{
    const struct method_function *func = b;

    return strcmp(a, func->name);
}

static bool add_method_call(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *object,
        const char *name, const struct parse_initializer *params, const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    const struct method_function *method;

    if (object_type->class == HLSL_CLASS_ERROR)
    {
        block->value = ctx->error_instr;
        return true;
    }

    for (unsigned int i = 0; i < params->args_count; ++i)
    {
        if (params->args[i]->data_type->class == HLSL_CLASS_ERROR)
        {
            block->value = ctx->error_instr;
            return true;
        }
    }

    if (object_type->class == HLSL_CLASS_TEXTURE)
    {
        method = bsearch(name, texture_methods, ARRAY_SIZE(texture_methods), sizeof(*method),
                object_method_function_name_compare);

        if (method && method->valid_dims[object_type->sampler_dim] != '1')
            method = NULL;
    }
    else if (object_type->class == HLSL_CLASS_UAV)
    {
        method = bsearch(name, uav_methods, ARRAY_SIZE(uav_methods), sizeof(*method),
                object_method_function_name_compare);

        if (method && method->valid_dims[object_type->sampler_dim] != '1')
            method = NULL;
    }
    else if (object_type->class == HLSL_CLASS_STREAM_OUTPUT)
    {
        method = bsearch(name, so_methods, ARRAY_SIZE(so_methods), sizeof(*method),
                object_method_function_name_compare);
    }
    else
    {
        struct vkd3d_string_buffer *string;

        if ((string = hlsl_type_to_string(ctx, object_type)))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "Type '%s' does not have methods.", string->buffer);
        hlsl_release_string_buffer(ctx, string);
        return false;
    }

    if (method)
        return method->handler(ctx, block, object, name, params, loc);
    else
        return raise_invalid_method_object_type(ctx, object_type, name, loc);
}

static bool add_object_property_access(struct hlsl_ctx *ctx,
        struct hlsl_block *block, struct hlsl_ir_node *object, const char *name,
        const struct vkd3d_shader_location *loc)
{
    const struct hlsl_type *object_type = object->data_type;
    struct hlsl_resource_load_params load_params;
    struct hlsl_ir_node *zero;
    unsigned int sampler_dim;

    if (!strcmp(name, "Length"))
    {
        if (object_type->class != HLSL_CLASS_TEXTURE && object_type->class != HLSL_CLASS_UAV)
            return false;

        sampler_dim = hlsl_sampler_dim_count(object_type->sampler_dim);

        switch (object_type->sampler_dim)
        {
            case HLSL_SAMPLER_DIM_1D:
            case HLSL_SAMPLER_DIM_2D:
            case HLSL_SAMPLER_DIM_3D:
            case HLSL_SAMPLER_DIM_1DARRAY:
            case HLSL_SAMPLER_DIM_2DARRAY:
            case HLSL_SAMPLER_DIM_2DMS:
            case HLSL_SAMPLER_DIM_2DMSARRAY:
                break;

            case HLSL_SAMPLER_DIM_BUFFER:
            case HLSL_SAMPLER_DIM_STRUCTURED_BUFFER:
                hlsl_fixme(ctx, loc, "'Length' property for buffers.");
                block->value = ctx->error_instr;
                return true;

            default:
                return false;
        }

        if (hlsl_version_lt(ctx, 4, 0))
        {
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INCOMPATIBLE_PROFILE,
                    "'Length' property can only be used on profiles 4.0 or higher.");
            block->value = ctx->error_instr;
            return true;
        }

        zero = hlsl_block_add_uint_constant(ctx, block, 0, loc);

        memset(&load_params, 0, sizeof(load_params));
        load_params.type = HLSL_RESOURCE_RESINFO;
        load_params.resource = object;
        load_params.lod = zero;
        load_params.format = hlsl_get_vector_type(ctx, HLSL_TYPE_UINT, sampler_dim);
        hlsl_block_add_resource_load(ctx, block, &load_params, loc);

        return true;
    }

    return false;
}

static void validate_texture_format_type(struct hlsl_ctx *ctx, enum hlsl_sampler_dim dim,
        struct hlsl_type *format, const struct vkd3d_shader_location *loc)
{
    struct vkd3d_string_buffer *string;

    if (!(string = hlsl_type_to_string(ctx, format)))
        return;

    if (dim == HLSL_SAMPLER_DIM_STRUCTURED_BUFFER)
    {
        if (!type_contains_only_numerics(format))
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "SRV type %s is not numeric.", string->buffer);
    }
    else if (format->class > HLSL_CLASS_VECTOR)
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                "Texture data type %s is not scalar or vector.", string->buffer);

    hlsl_release_string_buffer(ctx, string);
}

static bool check_continue(struct hlsl_ctx *ctx, const struct hlsl_scope *scope, const struct vkd3d_shader_location *loc)
{
    if (scope->_switch)
    {
        hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                "The 'continue' statement is not allowed in 'switch' statements.");
        return false;
    }

    if (scope->loop)
        return true;

    if (scope->upper)
        return check_continue(ctx, scope->upper, loc);

    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX, "The 'continue' statement is only allowed in loops.");
    return false;
}

static bool is_break_allowed(const struct hlsl_scope *scope)
{
    if (scope->loop || scope->_switch)
        return true;

    return scope->upper ? is_break_allowed(scope->upper) : false;
}

static void check_duplicated_switch_cases(struct hlsl_ctx *ctx, const struct hlsl_ir_switch_case *check, struct list *cases)
{
    struct hlsl_ir_switch_case *c;
    bool found_duplicate = false;

    LIST_FOR_EACH_ENTRY(c, cases, struct hlsl_ir_switch_case, entry)
    {
        if (check->is_default)
        {
            if ((found_duplicate = c->is_default))
            {
                hlsl_error(ctx, &check->loc, VKD3D_SHADER_ERROR_HLSL_DUPLICATE_SWITCH_CASE,
                        "Found multiple 'default' statements.");
                hlsl_note(ctx, &c->loc, VKD3D_SHADER_LOG_ERROR, "The 'default' statement was previously found here.");
            }
        }
        else
        {
            if (c->is_default) continue;
            if ((found_duplicate = (c->value == check->value)))
            {
                hlsl_error(ctx, &check->loc, VKD3D_SHADER_ERROR_HLSL_DUPLICATE_SWITCH_CASE,
                        "Found duplicate 'case' statement.");
                hlsl_note(ctx, &c->loc, VKD3D_SHADER_LOG_ERROR, "The same 'case %d' statement was previously found here.",
                        c->value);
            }
        }

        if (found_duplicate)
            break;
    }
}

static bool add_switch(struct hlsl_ctx *ctx, struct hlsl_block *block,
        struct parse_attribute_list *attributes, struct list *cases, const struct vkd3d_shader_location *loc)
{
    struct hlsl_ir_node *selector = node_from_block(block);
    struct hlsl_ir_node *s;

    if (selector->data_type->class == HLSL_CLASS_ERROR)
    {
        destroy_switch_cases(cases);
        destroy_block(block);
        cleanup_parse_attribute_list(attributes);
        return true;
    }

    selector = add_implicit_conversion(ctx, block, selector,
            hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), &selector->loc);
    s = hlsl_new_switch(ctx, selector, cases, loc);

    destroy_switch_cases(cases);

    if (!s)
    {
        destroy_block(block);
        cleanup_parse_attribute_list(attributes);
        return false;
    }

    hlsl_block_add_instr(block, s);

    cleanup_parse_attribute_list(attributes);
    return true;
}

static void validate_uav_type(struct hlsl_ctx *ctx, enum hlsl_sampler_dim dim,
        struct hlsl_type *format, const struct vkd3d_shader_location* loc)
{
    struct vkd3d_string_buffer *string = hlsl_type_to_string(ctx, format);

    if (!type_contains_only_numerics(format))
    {
        if (string)
            hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                    "UAV type %s is not numeric.", string->buffer);
    }

    switch (dim)
    {
        case HLSL_SAMPLER_DIM_BUFFER:
        case HLSL_SAMPLER_DIM_1D:
        case HLSL_SAMPLER_DIM_1DARRAY:
        case HLSL_SAMPLER_DIM_2D:
        case HLSL_SAMPLER_DIM_2DARRAY:
        case HLSL_SAMPLER_DIM_3D:
            if (format->class == HLSL_CLASS_ARRAY)
            {
                if (string)
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "This type of UAV does not support array type.");
            }
            else if (hlsl_type_component_count(format) > 4)
            {
                if (string)
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "UAV data type %s size exceeds maximum size.", string->buffer);
            }
            break;
        case HLSL_SAMPLER_DIM_STRUCTURED_BUFFER:
            break;
        default:
            vkd3d_unreachable();
    }

    hlsl_release_string_buffer(ctx, string);
}

}

%locations
%define parse.error verbose
%define api.prefix {hlsl_yy}
%define api.pure full
%expect 1
%lex-param {yyscan_t scanner}
%parse-param {void *scanner}
%parse-param {struct hlsl_ctx *ctx}

%union
{
    struct hlsl_type *type;
    INT intval;
    FLOAT floatval;
    bool boolval;
    char *name;
    uint32_t modifiers;
    struct hlsl_block *block;
    struct list *list;
    struct parse_fields fields;
    struct parse_function function;
    struct parse_parameter parameter;
    struct hlsl_func_parameters parameters;
    struct parse_initializer initializer;
    struct parse_array_sizes arrays;
    struct parse_variable_def *variable_def;
    struct parse_if_body if_body;
    enum parse_assign_op assign_op;
    struct hlsl_reg_reservation reg_reservation;
    struct parse_colon_attributes colon_attributes;
    struct hlsl_semantic semantic;
    enum hlsl_buffer_type buffer_type;
    enum hlsl_sampler_dim sampler_dim;
    enum hlsl_so_object_type so_type;
    enum hlsl_array_type patch_type;
    struct hlsl_attribute *attr;
    struct parse_attribute_list attr_list;
    struct hlsl_ir_switch_case *switch_case;
    struct hlsl_scope *scope;
    struct hlsl_state_block *state_block;
    struct state_block_index state_block_index;
}

%token KW_BLENDSTATE
%token KW_BREAK
%token KW_BUFFER
%token KW_BYTEADDRESSBUFFER
%token KW_CASE
%token KW_CONSTANTBUFFER
%token KW_CBUFFER
%token KW_CENTROID
%token KW_COLUMN_MAJOR
%token KW_COMPILE
%token KW_COMPILESHADER
%token KW_COMPUTESHADER
%token KW_CONST
%token KW_CONTINUE
%token KW_DEFAULT
%token KW_DEPTHSTENCILSTATE
%token KW_DEPTHSTENCILVIEW
%token KW_DISCARD
%token KW_DO
%token KW_DOMAINSHADER
%token KW_ELSE
%token KW_EXPORT
%token KW_EXTERN
%token KW_FALSE
%token KW_FOR
%token KW_FXGROUP
%token KW_GEOMETRYSHADER
%token KW_GROUPSHARED
%token KW_HULLSHADER
%token KW_IF
%token KW_IN
%token KW_INLINE
%token KW_INOUT
%token KW_INPUTPATCH
%token KW_LINE
%token KW_LINEADJ
%token KW_LINEAR
%token KW_LINESTREAM
%token KW_MATRIX
%token KW_NAMESPACE
%token KW_NOINTERPOLATION
%token KW_NOPERSPECTIVE
%token KW_NULL
%token KW_OUT
%token KW_OUTPUTPATCH
%token KW_PACKOFFSET
%token KW_PASS
%token KW_PIXELSHADER
%token KW_POINT
%token KW_POINTSTREAM
%token KW_RASTERIZERORDEREDBUFFER
%token KW_RASTERIZERORDEREDSTRUCTUREDBUFFER
%token KW_RASTERIZERORDEREDTEXTURE1D
%token KW_RASTERIZERORDEREDTEXTURE1DARRAY
%token KW_RASTERIZERORDEREDTEXTURE2D
%token KW_RASTERIZERORDEREDTEXTURE2DARRAY
%token KW_RASTERIZERORDEREDTEXTURE3D
%token KW_RASTERIZERSTATE
%token KW_RENDERTARGETVIEW
%token KW_RETURN
%token KW_REGISTER
%token KW_ROW_MAJOR
%token KW_RWBUFFER
%token KW_RWBYTEADDRESSBUFFER
%token KW_RWSTRUCTUREDBUFFER
%token KW_RWTEXTURE1D
%token KW_RWTEXTURE1DARRAY
%token KW_RWTEXTURE2D
%token KW_RWTEXTURE2DARRAY
%token KW_RWTEXTURE3D
%token KW_SAMPLER
%token KW_SAMPLER1D
%token KW_SAMPLER2D
%token KW_SAMPLER3D
%token KW_SAMPLERCUBE
%token KW_SAMPLER_STATE
%token KW_SAMPLERCOMPARISONSTATE
%token KW_SHARED
%token KW_SNORM
%token KW_STATEBLOCK
%token KW_STATEBLOCK_STATE
%token KW_STATIC
%token KW_STRING
%token KW_STRUCT
%token KW_STRUCTUREDBUFFER
%token KW_SWITCH
%token KW_TBUFFER
%token KW_TECHNIQUE
%token KW_TECHNIQUE10
%token KW_TECHNIQUE11
%token KW_TEXTURE
%token KW_TEXTURE1D
%token KW_TEXTURE1DARRAY
%token KW_TEXTURE2D
%token KW_TEXTURE2DARRAY
%token KW_TEXTURE2DMS
%token KW_TEXTURE2DMSARRAY
%token KW_TEXTURE3D
%token KW_TEXTURECUBE
%token KW_TEXTURECUBEARRAY
%token KW_TRIANGLE
%token KW_TRIANGLEADJ
%token KW_TRIANGLESTREAM
%token KW_TRUE
%token KW_TYPEDEF
%token KW_UNSIGNED
%token KW_UNIFORM
%token KW_UNORM
%token KW_VECTOR
%token KW_VERTEXSHADER
%token KW_VOID
%token KW_VOLATILE
%token KW_WHILE

%token OP_INC
%token OP_DEC
%token OP_AND
%token OP_OR
%token OP_EQ
%token OP_LEFTSHIFT
%token OP_LEFTSHIFTASSIGN
%token OP_RIGHTSHIFT
%token OP_RIGHTSHIFTASSIGN
%token OP_LE
%token OP_GE
%token OP_NE
%token OP_ADDASSIGN
%token OP_SUBASSIGN
%token OP_MULASSIGN
%token OP_DIVASSIGN
%token OP_MODASSIGN
%token OP_ANDASSIGN
%token OP_ORASSIGN
%token OP_XORASSIGN

%token <floatval> C_FLOAT

%token <intval> C_INTEGER
%token <intval> C_UNSIGNED
%token <intval> PRE_LINE

%type <list> type_specs
%type <list> variables_def
%type <list> variables_def_typed
%type <list> switch_cases
%destructor { destroy_parse_variable_defs($$); } type_specs variables_def variables_def_typed;
%destructor { destroy_switch_cases($$); } switch_cases;

%token <name> VAR_IDENTIFIER
%token <name> NEW_IDENTIFIER
%token <name> STRING
%token <name> TYPE_IDENTIFIER

%type <scope> annotations_opt

%type <arrays> arrays

%type <assign_op> assign_op

%type <attr> attribute

%type <attr_list> attribute_list
%type <attr_list> attribute_list_optional

%type <block> add_expr
%type <block> assignment_expr
%type <block> bitand_expr
%type <block> bitor_expr
%type <block> bitxor_expr
%type <block> compound_statement
%type <block> conditional_expr
%type <block> declaration
%type <block> declaration_statement
%type <block> equality_expr
%type <block> expr
%type <block> expr_optional
%type <block> expr_statement
%type <block> initializer_expr
%type <block> jump_statement
%type <block> logicand_expr
%type <block> logicor_expr
%type <block> loop_statement
%type <block> mul_expr
%type <block> postfix_expr
%type <block> primary_expr
%type <block> relational_expr
%type <block> shift_expr
%type <block> selection_statement
%type <block> statement
%type <block> statement_list
%type <block> switch_statement
%type <block> unary_expr
%destructor { destroy_block($$); } <block>

%type <boolval> boolean

%type <buffer_type> buffer_type

%type <colon_attributes> colon_attributes

%type <fields> field
%type <fields> fields_list

%type <function> func_prototype
%type <function> func_prototype_no_attrs

%type <initializer> complex_initializer
%type <initializer> complex_initializer_list
%type <initializer> func_arguments
%type <initializer> initializer_expr_list

%type <if_body> if_body

%type <intval> array

%type <modifiers> var_modifiers

%type <name> any_identifier
%type <name> var_identifier
%type <name> stateblock_lhs_identifier
%type <name> name_opt

%type <parameter> parameter
%type <parameter> parameter_decl

%type <parameters> param_list
%type <parameters> parameters

%type <reg_reservation> register_reservation
%type <reg_reservation> packoffset_reservation

%type <sampler_dim> texture_type texture_ms_type uav_type rov_type
%type <patch_type> patch_type

%type <semantic> semantic

%type <so_type> so_type

%type <state_block> state_block

%type <state_block_index> state_block_index_opt

%type <switch_case> switch_case
%destructor { hlsl_free_ir_switch_case($$); } <switch_case>

%type <type> base_optional
%type <type> field_type
%type <type> named_struct_spec
%type <type> unnamed_struct_spec
%type <type> struct_spec
%type <type> type
%type <type> type_no_void
%type <type> typedef_type
%type <type> resource_format

%type <variable_def> state_block_list
%type <variable_def> type_spec
%type <variable_def> variable_decl
%type <variable_def> variable_def
%type <variable_def> variable_def_typed
%destructor { free_parse_variable_def($$); } <variable_def>

%%

hlsl_prog:
      %empty
    | hlsl_prog func_declaration
    | hlsl_prog buffer_declaration buffer_body
    | hlsl_prog declaration_statement
        {
            hlsl_block_add_block(&ctx->static_initializers, $2);
            destroy_block($2);
        }
    | hlsl_prog preproc_directive
    | hlsl_prog global_technique
    | hlsl_prog effect_group
    | hlsl_prog ';'

name_opt:
      %empty
        {
            $$ = NULL;
        }
    | any_identifier

pass:
      KW_PASS name_opt annotations_opt '{' state_block_start state_block '}'
        {
            add_pass(ctx, $2, $3, $6, &@1);
        }

annotations_list:
      variables_def_typed ';'
        {
            struct hlsl_block *block;

            block = initialize_vars(ctx, $1);
            destroy_block(block);
        }
    | annotations_list variables_def_typed ';'
        {
            struct hlsl_block *block;

            block = initialize_vars(ctx, $2);
            destroy_block(block);
        }

annotations_opt:
      %empty
        {
            $$ = NULL;
        }
    | '<' annotations_scope_start '>'
        {
            hlsl_pop_scope(ctx);
            $$ = NULL;
        }
    | '<' annotations_scope_start annotations_list '>'
        {
            struct hlsl_scope *scope = ctx->cur_scope;

            hlsl_pop_scope(ctx);
            $$ = scope;
        }

pass_list:
      pass
    | pass_list pass

passes:
      scope_start
    | scope_start pass_list

technique9:
      KW_TECHNIQUE name_opt annotations_opt '{' passes '}'
        {
            struct hlsl_scope *scope = ctx->cur_scope;
            hlsl_pop_scope(ctx);

            add_technique(ctx, $2, scope, $3, "technique", &@1);
        }

technique10:
      KW_TECHNIQUE10 name_opt annotations_opt '{' passes '}'
        {
            struct hlsl_scope *scope = ctx->cur_scope;
            hlsl_pop_scope(ctx);

            add_technique(ctx, $2, scope, $3, "technique10", &@1);
        }

technique11:
      KW_TECHNIQUE11 name_opt annotations_opt '{' passes '}'
        {
            struct hlsl_scope *scope = ctx->cur_scope;
            hlsl_pop_scope(ctx);

            if (ctx->profile->type == VKD3D_SHADER_TYPE_EFFECT && ctx->profile->major_version == 2)
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                        "The 'technique11' keyword is invalid for this profile.");

            add_technique(ctx, $2, scope, $3, "technique11", &@1);
        }

global_technique:
      technique9
    | technique10
    | technique11

group_technique:
      technique10
    | technique11

group_techniques:
      group_technique
    | group_techniques group_technique

effect_group:
      KW_FXGROUP any_identifier annotations_opt '{' scope_start group_techniques '}'
        {
            struct hlsl_scope *scope = ctx->cur_scope;
            hlsl_pop_scope(ctx);
            add_effect_group(ctx, $2, scope, $3, &@2);
        }

buffer_declaration:
      var_modifiers buffer_type any_identifier colon_attributes annotations_opt
        {
            if ($4.semantic.name)
                hlsl_error(ctx, &@4, VKD3D_SHADER_ERROR_HLSL_INVALID_SEMANTIC, "Semantics are not allowed on buffers.");

            if (!(ctx->cur_buffer = hlsl_new_buffer(ctx, $2, $3, $1, &$4.reg_reservation, $5, &@3)))
                YYABORT;
        }

buffer_body:
      '{' declaration_statement_list '}'
        {
            ctx->cur_buffer = ctx->globals_buffer;
        }

buffer_type:
      KW_CBUFFER
        {
            $$ = HLSL_BUFFER_CONSTANT;
        }
    | KW_TBUFFER
        {
            $$ = HLSL_BUFFER_TEXTURE;
        }

declaration_statement_list:
      %empty
    | declaration_statement_list declaration_statement
        {
            destroy_block($2);
        }

preproc_directive:
      PRE_LINE STRING
        {
            if (strcmp($2, ctx->location.source_name))
            {
                if (!vkd3d_shader_source_list_append(ctx->source_files, $2))
                {
                    ctx->result = VKD3D_ERROR_OUT_OF_MEMORY;
                }
                else
                {
                    ctx->location.line = $1;
                    ctx->location.source_name = ctx->source_files->sources[ctx->source_files->count - 1];
                }
            }
            vkd3d_free($2);
        }

struct_declaration_without_vars:
      var_modifiers struct_spec ';'
        {
            if (!$2->name)
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                    "Anonymous struct type must declare a variable.");

            if ($1)
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                        "Modifiers are not allowed on struct type declarations.");
        }

struct_spec:
      named_struct_spec
    | unnamed_struct_spec

named_struct_spec:
      KW_STRUCT any_identifier base_optional '{' fields_list '}'
        {
            bool ret;

            if ($3)
            {
                char *name;

                if (!(name = hlsl_strdup(ctx, "$super")))
                    YYABORT;
                if (!hlsl_array_reserve(ctx, (void **)&$5.fields, &$5.capacity, 1 + $5.count, sizeof(*$5.fields)))
                    YYABORT;
                memmove(&$5.fields[1], $5.fields, $5.count * sizeof(*$5.fields));
                ++$5.count;

                memset(&$5.fields[0], 0, sizeof($5.fields[0]));
                $5.fields[0].type = $3;
                $5.fields[0].loc = @3;
                $5.fields[0].name = name;
            }

            $$ = hlsl_new_struct_type(ctx, $2, $5.fields, $5.count);

            if (hlsl_get_var(ctx->cur_scope, $2))
            {
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_REDEFINED, "\"%s\" is already declared as a variable.", $2);
                YYABORT;
            }

            ret = hlsl_scope_add_type(ctx->cur_scope, $$);
            if (!ret)
            {
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_REDEFINED, "Struct \"%s\" is already defined.", $2);
                YYABORT;
            }
        }

unnamed_struct_spec:
      KW_STRUCT '{' fields_list '}'
        {
            $$ = hlsl_new_struct_type(ctx, NULL, $3.fields, $3.count);
        }

any_identifier:
      VAR_IDENTIFIER
    | TYPE_IDENTIFIER
    | NEW_IDENTIFIER

/* TODO: Multiple inheritance support for interfaces. */
base_optional:
      %empty
        {
            $$ = NULL;
        }
    | ':' TYPE_IDENTIFIER
        {
            $$ = hlsl_get_type(ctx->cur_scope, $2, true, true);
            if ($$->class != HLSL_CLASS_STRUCT)
            {
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Base type \"%s\" is not a struct.", $2);
                YYABORT;
            }
            vkd3d_free($2);
        }

fields_list:
      %empty
        {
            $$.fields = NULL;
            $$.count = 0;
            $$.capacity = 0;
        }
    | fields_list field
        {
            size_t i;

            for (i = 0; i < $2.count; ++i)
            {
                const struct hlsl_struct_field *field = &$2.fields[i];
                const struct hlsl_struct_field *existing;

                if ((existing = get_struct_field($1.fields, $1.count, field->name)))
                {
                    hlsl_error(ctx, &field->loc, VKD3D_SHADER_ERROR_HLSL_REDEFINED,
                            "Field \"%s\" is already defined.", field->name);
                    hlsl_note(ctx, &existing->loc, VKD3D_SHADER_LOG_ERROR,
                            "'%s' was previously defined here.", field->name);
                }
            }

            if (!hlsl_array_reserve(ctx, (void **)&$1.fields, &$1.capacity, $1.count + $2.count, sizeof(*$1.fields)))
                YYABORT;
            memcpy($1.fields + $1.count, $2.fields, $2.count * sizeof(*$2.fields));
            $1.count += $2.count;
            vkd3d_free($2.fields);

            $$ = $1;
        }

field_type:
      type
    | unnamed_struct_spec

field:
      var_modifiers field_type variables_def ';'
        {
            struct hlsl_type *type;
            uint32_t modifiers = $1;

            if (!(type = apply_type_modifiers(ctx, $2, &modifiers, true, &@1)))
                YYABORT;
            if (modifiers & ~HLSL_INTERPOLATION_MODIFIERS_MASK)
            {
                struct vkd3d_string_buffer *string;

                if ((string = hlsl_modifiers_to_string(ctx, modifiers)))
                    hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                            "Modifiers '%s' are not allowed on struct fields.", string->buffer);
                hlsl_release_string_buffer(ctx, string);
            }
            if (!gen_struct_fields(ctx, &$$, type, modifiers, $3))
                YYABORT;
        }

attribute:
      '[' any_identifier ']'
        {
            if (!($$ = hlsl_alloc(ctx, offsetof(struct hlsl_attribute, args[0]))))
            {
                vkd3d_free($2);
                YYABORT;
            }
            $$->name = $2;
            hlsl_block_init(&$$->instrs);
            $$->loc = @$;
            $$->args_count = 0;
        }
    | '[' any_identifier '(' initializer_expr_list ')' ']'
        {
            unsigned int i;

            if (!($$ = hlsl_alloc(ctx, offsetof(struct hlsl_attribute, args[$4.args_count]))))
            {
                vkd3d_free($2);
                free_parse_initializer(&$4);
                YYABORT;
            }
            $$->name = $2;
            hlsl_block_init(&$$->instrs);
            hlsl_block_add_block(&$$->instrs, $4.instrs);
            $$->loc = @$;
            $$->args_count = $4.args_count;
            for (i = 0; i < $4.args_count; ++i)
                hlsl_src_from_node(&$$->args[i], $4.args[i]);
            free_parse_initializer(&$4);
        }

attribute_list:
      attribute
        {
            $$.count = 1;
            if (!($$.attrs = hlsl_alloc(ctx, sizeof(*$$.attrs))))
            {
                hlsl_free_attribute($1);
                YYABORT;
            }
            $$.attrs[0] = $1;
        }
    | attribute_list attribute
        {
            const struct hlsl_attribute **new_array;

            $$ = $1;
            if (!(new_array = vkd3d_realloc($$.attrs, ($$.count + 1) * sizeof(*$$.attrs))))
            {
                cleanup_parse_attribute_list(&$$);
                YYABORT;
            }
            $$.attrs = new_array;
            $$.attrs[$$.count++] = $2;
        }

attribute_list_optional:
      %empty
        {
            $$.count = 0;
            $$.attrs = NULL;
        }
      | attribute_list

func_declaration:
      func_prototype compound_statement
        {
            struct hlsl_ir_function_decl *decl = $1.decl;

            if (decl->has_body)
            {
                hlsl_error(ctx, &decl->loc, VKD3D_SHADER_ERROR_HLSL_REDEFINED,
                         "Function \"%s\" is already defined.", decl->func->name);
                hlsl_note(ctx, &decl->loc, VKD3D_SHADER_LOG_ERROR,
                         "\"%s\" was previously defined here.", decl->func->name);
                destroy_block($2);
            }
            else
            {
                size_t i;

                decl->has_body = true;
                hlsl_block_add_block(&decl->body, $2);
                destroy_block($2);

                /* Semantics are taken from whichever definition has a body.
                 * We can't just replace the hlsl_ir_var pointers, though: if
                 * the function was already declared but not defined, the
                 * callers would have used the old declaration's parameters to
                 * transfer arguments. */

                if (!$1.first)
                {
                    VKD3D_ASSERT(decl->parameters.count == $1.parameters.count);

                    for (i = 0; i < $1.parameters.count; ++i)
                    {
                        struct hlsl_ir_var *dst = decl->parameters.vars[i];
                        struct hlsl_ir_var *src = $1.parameters.vars[i];

                        hlsl_cleanup_semantic(&dst->semantic);
                        dst->semantic = src->semantic;
                        memset(&src->semantic, 0, sizeof(src->semantic));
                    }

                    if (decl->return_var)
                    {
                        hlsl_cleanup_semantic(&decl->return_var->semantic);
                        decl->return_var->semantic = $1.return_semantic;
                        memset(&$1.return_semantic, 0, sizeof($1.return_semantic));
                    }
                }
            }
            hlsl_pop_scope(ctx);

            if (!$1.first)
            {
                vkd3d_free($1.parameters.vars);
                hlsl_cleanup_semantic(&$1.return_semantic);
            }
        }
    | func_prototype ';'
        {
            hlsl_pop_scope(ctx);
        }

func_prototype_no_attrs:
    /* var_modifiers is necessary to avoid shift/reduce conflicts. */
      var_modifiers type var_identifier '(' parameters ')' colon_attributes
        {
            uint32_t modifiers = $1;
            struct hlsl_ir_var *var;
            struct hlsl_type *type;

            /* Functions are unconditionally inlined. */
            modifiers &= ~HLSL_MODIFIER_INLINE;

            if (modifiers & ~(HLSL_MODIFIERS_MAJORITY_MASK | HLSL_MODIFIER_EXPORT))
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                        "Unexpected modifier used on a function.");
            if (!(type = apply_type_modifiers(ctx, $2, &modifiers, true, &@1)))
                YYABORT;
            if ((var = hlsl_get_var(ctx->globals, $3)))
            {
                hlsl_error(ctx, &@3, VKD3D_SHADER_ERROR_HLSL_REDEFINED,
                        "\"%s\" is already declared as a variable.", $3);
                hlsl_note(ctx, &var->loc, VKD3D_SHADER_LOG_ERROR,
                        "\"%s\" was previously declared here.", $3);
            }
            if (hlsl_types_are_equal(type, ctx->builtin_types.Void) && $7.semantic.name)
            {
                hlsl_error(ctx, &@7, VKD3D_SHADER_ERROR_HLSL_INVALID_SEMANTIC,
                        "Semantics are not allowed on void functions.");
            }

            if ($7.reg_reservation.reg_type)
                FIXME("Unexpected register reservation for a function.\n");
            if ($7.reg_reservation.offset_type)
                hlsl_error(ctx, &@5, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "packoffset() is not allowed on functions.");

            if (($$.decl = hlsl_get_func_decl(ctx, $3, &$5)))
            {
                const struct hlsl_func_parameters *params = &$$.decl->parameters;
                size_t i;

                if (!hlsl_types_are_equal($2, $$.decl->return_type))
                {
                    hlsl_error(ctx, &@3, VKD3D_SHADER_ERROR_HLSL_REDEFINED,
                            "\"%s\" was already declared with a different return type.", $3);
                    hlsl_note(ctx, &$$.decl->loc, VKD3D_SHADER_LOG_ERROR, "\"%s\" was previously declared here.", $3);
                }

                vkd3d_free($3);

                /* We implement function invocation by copying to input
                 * parameters, emitting a HLSL_IR_CALL instruction, then copying
                 * from output parameters. As a result, we need to use the same
                 * parameter variables for every invocation of this function,
                 * which means we use the parameters created by the first
                 * declaration. If we're not the first declaration, the
                 * parameter variables that just got created will end up being
                 * mostly ignored—in particular, they won't be used in actual
                 * IR.
                 *
                 * There is a hitch: if this is the actual definition, the
                 * function body will look up parameter variables by name. We
                 * must return the original parameters, and not the ones we just
                 * created, but we're in the wrong scope, and the parameters
                 * might not even have the same names.
                 *
                 * Therefore we need to shuffle the parameters we just created
                 * into a dummy scope where they'll never be looked up, and
                 * rename the original parameters so they have the expected
                 * names. We actually do this for every prototype: we don't know
                 * whether this is the function definition yet, but it doesn't
                 * really matter. The variables can only be used in the
                 * actual definition, and don't do anything in a declaration.
                 *
                 * This is complex, and it seems tempting to avoid this logic by
                 * putting arguments into the HLSL_IR_CALL instruction, letting
                 * the canonical variables be the ones attached to the function
                 * definition, and resolving the copies when inlining. The
                 * problem with this is output parameters. We would have to use
                 * a lot of parsing logic on already lowered IR, which is
                 * brittle and ugly.
                 */

                VKD3D_ASSERT($5.count == params->count);
                for (i = 0; i < params->count; ++i)
                {
                    struct hlsl_ir_var *orig_param = params->vars[i];
                    struct hlsl_ir_var *new_param = $5.vars[i];
                    char *new_name;

                    list_remove(&orig_param->scope_entry);
                    list_add_tail(&ctx->cur_scope->vars, &orig_param->scope_entry);

                    list_remove(&new_param->scope_entry);
                    list_add_tail(&ctx->dummy_scope->vars, &new_param->scope_entry);

                    if (!(new_name = hlsl_strdup(ctx, new_param->name)))
                        YYABORT;
                    vkd3d_free((void *)orig_param->name);
                    orig_param->name = new_name;
                }

                $$.first = false;
                $$.parameters = $5;
                $$.return_semantic = $7.semantic;
            }
            else
            {
                if (!($$.decl = hlsl_new_func_decl(ctx, type, &$5, &$7.semantic, &@3)))
                    YYABORT;

                hlsl_add_function(ctx, $3, $$.decl);

                $$.first = true;
            }

            ctx->cur_function = $$.decl;
        }

func_prototype:
      func_prototype_no_attrs
    | attribute_list func_prototype_no_attrs
        {
            check_attribute_list_for_duplicates(ctx, &$1);

            if ($2.first)
            {
                $2.decl->attr_count = $1.count;
                $2.decl->attrs = $1.attrs;
            }
            else
            {
                cleanup_parse_attribute_list(&$1);
            }
            $$ = $2;
        }

compound_statement:
      '{' '}'
        {
            if (!($$ = make_empty_block(ctx)))
                YYABORT;
        }
    | '{' statement_list '}'
        {
            $$ = $2;
        }

scope_start:
      %empty
        {
            hlsl_push_scope(ctx);
        }

loop_scope_start:
      %empty
        {
            hlsl_push_scope(ctx);
            ctx->cur_scope->loop = true;
        }

switch_scope_start:
      %empty
        {
            hlsl_push_scope(ctx);
            ctx->cur_scope->_switch = true;
        }

annotations_scope_start:
      %empty
        {
            hlsl_push_scope(ctx);
            ctx->cur_scope->annotations = true;
        }

var_identifier:
      VAR_IDENTIFIER
    | NEW_IDENTIFIER

colon_attributes:
      %empty
        {
            $$.semantic = (struct hlsl_semantic){0};
            $$.reg_reservation.reg_type = 0;
            $$.reg_reservation.offset_type = 0;
        }
    | colon_attributes semantic
        {
            hlsl_cleanup_semantic(&$$.semantic);
            $$.semantic = $2;
        }
    | colon_attributes register_reservation
        {
            if ($$.reg_reservation.reg_type)
                hlsl_fixme(ctx, &@2, "Multiple register() reservations.");

            $$.reg_reservation.reg_type = $2.reg_type;
            $$.reg_reservation.reg_index = $2.reg_index;
            $$.reg_reservation.reg_space = $2.reg_space;
        }
    | colon_attributes packoffset_reservation
        {
            if (ctx->cur_buffer == ctx->globals_buffer)
            {
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "The packoffset() reservation is only allowed within 'cbuffer' blocks.");
            }
            else
            {
                $$.reg_reservation.offset_type = $2.offset_type;
                $$.reg_reservation.offset_index = $2.offset_index;
            }
        }

semantic:
      ':' any_identifier
        {
            static const char *centroid_suffix = "_centroid";
            uint32_t modifiers = 0;
            size_t len;
            char *p;

            if (!($$.raw_name = hlsl_strdup(ctx, $2)))
                YYABORT;

            len = strlen($2);
            if (ascii_strncasecmp($2, "sv_", 3)
                    && len > strlen(centroid_suffix)
                    && !ascii_strcasecmp($2 + (len - strlen(centroid_suffix)), centroid_suffix))
            {
                modifiers = HLSL_STORAGE_CENTROID;
                len -= strlen(centroid_suffix);
            }

            for (p = $2 + len; p > $2 && isdigit(p[-1]); --p)
                ;
            $$.name = $2;
            $$.index = atoi(p);
            $$.modifiers = modifiers;
            $$.reported_missing = false;
            $$.reported_duplicated_output_next_index = 0;
            $$.reported_duplicated_input_incompatible_next_index = 0;
            *p = 0;
        }

/* FIXME: Writemasks */
register_reservation:
      ':' KW_REGISTER '(' any_identifier ')'
        {
            memset(&$$, 0, sizeof($$));
            if (!parse_reservation_index(ctx, $4, 0, &$$))
                hlsl_error(ctx, &@4, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register reservation '%s'.", $4);

            vkd3d_free($4);
        }
    | ':' KW_REGISTER '(' any_identifier '[' expr ']' ')'
        {
            memset(&$$, 0, sizeof($$));
            if (!parse_reservation_index(ctx, $4, evaluate_static_expression_as_uint(ctx, $6, &@6), &$$))
            {
                hlsl_error(ctx, &@4, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register reservation '%s'.", $4);
            }

            vkd3d_free($4);
            vkd3d_free($6);
        }
    | ':' KW_REGISTER '(' any_identifier ',' any_identifier ')'
        {
            memset(&$$, 0, sizeof($$));
            if (parse_reservation_index(ctx, $6, 0, &$$))
            {
                hlsl_fixme(ctx, &@4, "Reservation shader target %s.", $4);
            }
            else if (parse_reservation_space($6, &$$.reg_space))
            {
                if (!parse_reservation_index(ctx, $4, 0, &$$))
                    hlsl_error(ctx, &@4, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                            "Invalid register reservation '%s'.", $4);
            }
            else
            {
                hlsl_error(ctx, &@6, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register or space reservation '%s'.", $6);
            }

            vkd3d_free($4);
            vkd3d_free($6);
        }
    | ':' KW_REGISTER '(' any_identifier '[' expr ']' ',' any_identifier ')'
        {
            memset(&$$, 0, sizeof($$));

            if (!parse_reservation_space($9, &$$.reg_space))
                hlsl_error(ctx, &@9, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register space reservation '%s'.", $9);

            if (!parse_reservation_index(ctx, $4, evaluate_static_expression_as_uint(ctx, $6, &@6), &$$))
            {
                hlsl_error(ctx, &@4, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register reservation '%s'.", $4);
            }

            vkd3d_free($4);
            vkd3d_free($6);
            vkd3d_free($9);
        }
    | ':' KW_REGISTER '(' any_identifier ',' any_identifier '[' expr ']' ')'
        {
            hlsl_fixme(ctx, &@4, "Reservation shader target %s.", $4);

            memset(&$$, 0, sizeof($$));
            if (!parse_reservation_index(ctx, $6, evaluate_static_expression_as_uint(ctx, $8, &@8), &$$))
            {
                hlsl_error(ctx, &@6, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register reservation '%s'.", $6);
            }

            vkd3d_free($4);
            vkd3d_free($6);
            vkd3d_free($8);
        }
    | ':' KW_REGISTER '(' any_identifier ',' any_identifier ',' any_identifier ')'
        {
            hlsl_fixme(ctx, &@4, "Reservation shader target %s.", $4);

            memset(&$$, 0, sizeof($$));
            if (!parse_reservation_index(ctx, $6, 0, &$$))
                hlsl_error(ctx, &@6, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register reservation '%s'.", $6);

            if (!parse_reservation_space($8, &$$.reg_space))
                hlsl_error(ctx, &@8, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register space reservation '%s'.", $8);

            vkd3d_free($4);
            vkd3d_free($6);
            vkd3d_free($8);
        }
    | ':' KW_REGISTER '(' any_identifier ',' any_identifier '[' expr ']' ',' any_identifier ')'
        {
            hlsl_fixme(ctx, &@4, "Reservation shader target %s.", $4);

            memset(&$$, 0, sizeof($$));
            if (!parse_reservation_index(ctx, $6, evaluate_static_expression_as_uint(ctx, $8, &@8), &$$))
            {
                hlsl_error(ctx, &@6, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register reservation '%s'.", $6);
            }

            if (!parse_reservation_space($11, &$$.reg_space))
                hlsl_error(ctx, &@11, VKD3D_SHADER_ERROR_HLSL_INVALID_RESERVATION,
                        "Invalid register space reservation '%s'.", $11);

            vkd3d_free($4);
            vkd3d_free($6);
            vkd3d_free($8);
            vkd3d_free($11);
        }

packoffset_reservation:
      ':' KW_PACKOFFSET '(' any_identifier ')'
        {
            $$ = parse_packoffset(ctx, $4, NULL, &@$);

            vkd3d_free($4);
        }
    | ':' KW_PACKOFFSET '(' any_identifier '.' any_identifier ')'
        {
            $$ = parse_packoffset(ctx, $4, $6, &@$);

            vkd3d_free($4);
            vkd3d_free($6);
        }

parameters:
      scope_start
        {
            memset(&$$, 0, sizeof($$));
        }
    | scope_start KW_VOID
        {
            memset(&$$, 0, sizeof($$));
        }
    | scope_start param_list
        {
            $$ = $2;
        }

param_list:
      parameter
        {
            memset(&$$, 0, sizeof($$));
            if (!add_func_parameter(ctx, &$$, &$1, &@1))
            {
                ERR("Error adding function parameter %s.\n", $1.name);
                YYABORT;
            }
        }
    | param_list ',' parameter
        {
            $$ = $1;
            if (!add_func_parameter(ctx, &$$, &$3, &@3))
                YYABORT;
        }

parameter:
      parameter_decl
    | parameter_decl '=' complex_initializer
        {
            $$ = $1;
            $$.initializer = $3;
        }

parameter_decl:
      var_modifiers type_no_void any_identifier arrays colon_attributes
        {
            uint32_t prim_modifiers = $1 & HLSL_PRIMITIVE_MODIFIERS_MASK;
            uint32_t modifiers = $1 & ~HLSL_PRIMITIVE_MODIFIERS_MASK;
            struct hlsl_type *type;
            unsigned int i;

            if (!(type = apply_type_modifiers(ctx, $2, &modifiers, true, &@1)))
                YYABORT;

            $$.modifiers = modifiers;
            if (!($$.modifiers & (HLSL_STORAGE_IN | HLSL_STORAGE_OUT)))
                $$.modifiers |= HLSL_STORAGE_IN;

            for (i = 0; i < $4.count; ++i)
            {
                if ($4.sizes[i] == HLSL_ARRAY_ELEMENTS_COUNT_IMPLICIT)
                {
                    hlsl_error(ctx, &@3, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Implicit size arrays not allowed in function parameters.");
                    type = ctx->builtin_types.error;
                    break;
                }

                type = hlsl_new_array_type(ctx, type, $4.sizes[i], HLSL_ARRAY_GENERIC);
            }
            vkd3d_free($4.sizes);

            if (prim_modifiers && (prim_modifiers & (prim_modifiers - 1)))
            {
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                        "Primitive type modifiers are mutually exclusive.");
                prim_modifiers = 0;
            }

            if (prim_modifiers)
            {
                if (type->class != HLSL_CLASS_ARRAY)
                    hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                        "Primitive type modifiers can only be applied to arrays.");
                else
                    type->modifiers |= prim_modifiers;
            }

            $$.type = type;

            if (hlsl_version_ge(ctx, 5, 1) && type->class == HLSL_CLASS_ARRAY && hlsl_type_is_resource(type))
                hlsl_fixme(ctx, &@2, "Shader model 5.1+ resource array.");

            $$.name = $3;
            $$.semantic = $5.semantic;
            $$.reg_reservation = $5.reg_reservation;

            memset(&$$.initializer, 0, sizeof($$.initializer));
        }

texture_type:
      KW_BUFFER
        {
            $$ = HLSL_SAMPLER_DIM_BUFFER;
        }
    | KW_STRUCTUREDBUFFER
        {
            $$ = HLSL_SAMPLER_DIM_STRUCTURED_BUFFER;
        }
    | KW_TEXTURE1D
        {
            $$ = HLSL_SAMPLER_DIM_1D;
        }
    | KW_TEXTURE2D
        {
            $$ = HLSL_SAMPLER_DIM_2D;
        }
    | KW_TEXTURE3D
        {
            $$ = HLSL_SAMPLER_DIM_3D;
        }
    | KW_TEXTURECUBE
        {
            $$ = HLSL_SAMPLER_DIM_CUBE;
        }
    | KW_TEXTURE1DARRAY
        {
            $$ = HLSL_SAMPLER_DIM_1DARRAY;
        }
    | KW_TEXTURE2DARRAY
        {
            $$ = HLSL_SAMPLER_DIM_2DARRAY;
        }
    | KW_TEXTURECUBEARRAY
        {
            $$ = HLSL_SAMPLER_DIM_CUBEARRAY;
        }

texture_ms_type:
      KW_TEXTURE2DMS
        {
            $$ = HLSL_SAMPLER_DIM_2DMS;
        }
    | KW_TEXTURE2DMSARRAY
        {
            $$ = HLSL_SAMPLER_DIM_2DMSARRAY;
        }

uav_type:
      KW_RWBUFFER
        {
            $$ = HLSL_SAMPLER_DIM_BUFFER;
        }
    | KW_RWSTRUCTUREDBUFFER
        {
            $$ = HLSL_SAMPLER_DIM_STRUCTURED_BUFFER;
        }
    | KW_RWTEXTURE1D
        {
            $$ = HLSL_SAMPLER_DIM_1D;
        }
    | KW_RWTEXTURE1DARRAY
        {
            $$ = HLSL_SAMPLER_DIM_1DARRAY;
        }
    | KW_RWTEXTURE2D
        {
            $$ = HLSL_SAMPLER_DIM_2D;
        }
    | KW_RWTEXTURE2DARRAY
        {
            $$ = HLSL_SAMPLER_DIM_2DARRAY;
        }
    | KW_RWTEXTURE3D
        {
            $$ = HLSL_SAMPLER_DIM_3D;
        }

rov_type:
      KW_RASTERIZERORDEREDBUFFER
        {
            $$ = HLSL_SAMPLER_DIM_BUFFER;
        }
    | KW_RASTERIZERORDEREDSTRUCTUREDBUFFER
        {
            $$ = HLSL_SAMPLER_DIM_STRUCTURED_BUFFER;
        }
    | KW_RASTERIZERORDEREDTEXTURE1D
        {
            $$ = HLSL_SAMPLER_DIM_1D;
        }
    | KW_RASTERIZERORDEREDTEXTURE1DARRAY
        {
            $$ = HLSL_SAMPLER_DIM_1DARRAY;
        }
    | KW_RASTERIZERORDEREDTEXTURE2D
        {
            $$ = HLSL_SAMPLER_DIM_2D;
        }
    | KW_RASTERIZERORDEREDTEXTURE2DARRAY
        {
            $$ = HLSL_SAMPLER_DIM_2DARRAY;
        }
    | KW_RASTERIZERORDEREDTEXTURE3D
        {
            $$ = HLSL_SAMPLER_DIM_3D;
        }

so_type:
      KW_POINTSTREAM
        {
            $$ = HLSL_STREAM_OUTPUT_POINT_STREAM;
        }
    | KW_LINESTREAM
        {
            $$ = HLSL_STREAM_OUTPUT_LINE_STREAM;
        }
    | KW_TRIANGLESTREAM
        {
            $$ = HLSL_STREAM_OUTPUT_TRIANGLE_STREAM;
        }

resource_format:
      var_modifiers type
        {
            uint32_t modifiers = $1;

            if (!($$ = apply_type_modifiers(ctx, $2, &modifiers, true, &@1)))
                YYABORT;
        }

patch_type:
      KW_INPUTPATCH
        {
            $$ = HLSL_ARRAY_PATCH_INPUT;
        }
    | KW_OUTPUTPATCH
        {
            $$ = HLSL_ARRAY_PATCH_OUTPUT;
        }

type_no_void:
      KW_VECTOR '<' type ',' C_INTEGER '>'
        {
            if ($3->class != HLSL_CLASS_SCALAR)
            {
                struct vkd3d_string_buffer *string;

                string = hlsl_type_to_string(ctx, $3);
                if (string)
                    hlsl_error(ctx, &@3, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Vector base type %s is not scalar.", string->buffer);
                hlsl_release_string_buffer(ctx, string);
                YYABORT;
            }
            if ($5 < 1 || $5 > 4)
            {
                hlsl_error(ctx, &@5, VKD3D_SHADER_ERROR_HLSL_INVALID_SIZE,
                        "Vector size %d is not between 1 and 4.", $5);
                YYABORT;
            }

            $$ = hlsl_type_clone(ctx, hlsl_get_vector_type(ctx, $3->e.numeric.type, $5), 0, 0);
            $$->is_minimum_precision = $3->is_minimum_precision;
        }
    | KW_VECTOR
        {
            $$ = hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, 4);
        }
    | KW_MATRIX '<' type ',' C_INTEGER ',' C_INTEGER '>'
        {
            if ($3->class != HLSL_CLASS_SCALAR)
            {
                struct vkd3d_string_buffer *string;

                string = hlsl_type_to_string(ctx, $3);
                if (string)
                    hlsl_error(ctx, &@3, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Matrix base type %s is not scalar.", string->buffer);
                hlsl_release_string_buffer(ctx, string);
                YYABORT;
            }
            if ($5 < 1 || $5 > 4)
            {
                hlsl_error(ctx, &@5, VKD3D_SHADER_ERROR_HLSL_INVALID_SIZE,
                        "Matrix row count %d is not between 1 and 4.", $5);
                YYABORT;
            }
            if ($7 < 1 || $7 > 4)
            {
                hlsl_error(ctx, &@7, VKD3D_SHADER_ERROR_HLSL_INVALID_SIZE,
                        "Matrix column count %d is not between 1 and 4.", $7);
                YYABORT;
            }

            $$ = hlsl_type_clone(ctx, hlsl_get_matrix_type(ctx, $3->e.numeric.type, $7, $5), 0, 0);
            $$->is_minimum_precision = $3->is_minimum_precision;
        }
    | KW_MATRIX
        {
            $$ = hlsl_get_matrix_type(ctx, HLSL_TYPE_FLOAT, 4, 4);
        }
    | KW_SAMPLER
        {
            $$ = ctx->builtin_types.sampler[HLSL_SAMPLER_DIM_GENERIC];
        }
    | KW_SAMPLERCOMPARISONSTATE
        {
            $$ = ctx->builtin_types.sampler[HLSL_SAMPLER_DIM_COMPARISON];
        }
    | KW_SAMPLER1D
        {
            $$ = ctx->builtin_types.sampler[HLSL_SAMPLER_DIM_1D];
        }
    | KW_SAMPLER2D
        {
            $$ = ctx->builtin_types.sampler[HLSL_SAMPLER_DIM_2D];
        }
    | KW_SAMPLER3D
        {
            $$ = ctx->builtin_types.sampler[HLSL_SAMPLER_DIM_3D];
        }
    | KW_SAMPLERCUBE
        {
            $$ = ctx->builtin_types.sampler[HLSL_SAMPLER_DIM_CUBE];
        }
    | KW_TEXTURE
        {
            $$ = hlsl_new_texture_type(ctx, HLSL_SAMPLER_DIM_GENERIC, NULL, 0);
        }
    | texture_type
        {
            if ($1 == HLSL_SAMPLER_DIM_STRUCTURED_BUFFER)
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "Structured buffer type requires an explicit format.");
            $$ = hlsl_new_texture_type(ctx, $1, hlsl_get_vector_type(ctx, HLSL_TYPE_FLOAT, 4), 0);
        }
    | texture_type '<' resource_format '>'
        {
            validate_texture_format_type(ctx, $1, $3, &@3);
            $$ = hlsl_new_texture_type(ctx, $1, $3, 0);
        }
    | texture_ms_type '<' resource_format '>'
        {
            validate_texture_format_type(ctx, $1, $3, &@3);

            $$ = hlsl_new_texture_type(ctx, $1, $3, 0);
        }
    | texture_ms_type '<' resource_format ',' shift_expr '>'
        {
            unsigned int sample_count;
            struct hlsl_block block;

            hlsl_block_init(&block);
            hlsl_block_add_block(&block, $5);

            sample_count = evaluate_static_expression_as_uint(ctx, &block, &@5);

            hlsl_block_cleanup(&block);

            vkd3d_free($5);

            $$ = hlsl_new_texture_type(ctx, $1, $3, sample_count);
        }
    | KW_BYTEADDRESSBUFFER
        {
            $$ = hlsl_new_texture_type(ctx, HLSL_SAMPLER_DIM_RAW_BUFFER, hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), 0);
        }
    | uav_type '<' resource_format '>'
        {
            validate_uav_type(ctx, $1, $3, &@3);
            $$ = hlsl_new_uav_type(ctx, $1, $3, false);
        }
    | rov_type '<' resource_format '>'
        {
            validate_uav_type(ctx, $1, $3, &@4);
            $$ = hlsl_new_uav_type(ctx, $1, $3, true);
        }
    | so_type '<' type '>'
        {
            $$ = hlsl_new_stream_output_type(ctx, $1, $3);
        }
    | patch_type '<' type ',' C_INTEGER '>'
        {
            struct hlsl_type *type;

            if ($5 < 1)
            {
                hlsl_error(ctx, &@5, VKD3D_SHADER_ERROR_HLSL_INVALID_SIZE,
                        "Control point size %d is not positive.", $5);
                YYABORT;
            }

            type = hlsl_new_array_type(ctx, $3, $5, $1);
            $$ = hlsl_type_clone(ctx, type, 0, HLSL_MODIFIER_CONST);
        }
    | KW_RWBYTEADDRESSBUFFER
        {
            $$ = hlsl_new_uav_type(ctx, HLSL_SAMPLER_DIM_RAW_BUFFER, hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), false);
        }
    | KW_STRING
        {
            $$ = ctx->builtin_types.string;
        }
    | TYPE_IDENTIFIER
        {
            $$ = hlsl_get_type(ctx->cur_scope, $1, true, true);
            if ($$->is_minimum_precision || hlsl_type_is_minimum_precision($$))
            {
                if (hlsl_version_lt(ctx, 4, 0))
                {
                    hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                            "Target profile doesn't support minimum-precision types.");
                }
                else if ($$->is_minimum_precision)
                {
                    FIXME("Reinterpreting type %s.\n", $$->name);
                }
            }
            vkd3d_free($1);
        }
    | KW_UNSIGNED TYPE_IDENTIFIER
        {
            struct hlsl_type *type = hlsl_get_type(ctx->cur_scope, $2, true, true);

            if (hlsl_is_numeric_type(type) && type->e.numeric.type == HLSL_TYPE_INT)
            {
                if (!(type = hlsl_type_clone(ctx, type, 0, 0)))
                    YYABORT;
                vkd3d_free((void *)type->name);
                type->name = NULL;
                type->e.numeric.type = HLSL_TYPE_UINT;
            }
            else
            {
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "The 'unsigned' keyword can't be used with type %s.", $2);
            }

            $$ = type;
        }
    | KW_STRUCT TYPE_IDENTIFIER
        {
            $$ = hlsl_get_type(ctx->cur_scope, $2, true, true);
            if ($$->class != HLSL_CLASS_STRUCT || $$->is_typedef)
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_REDEFINED, "\"%s\" is not a structure.", $2);
            vkd3d_free($2);
        }
    | KW_RENDERTARGETVIEW
        {
            $$ = hlsl_get_type(ctx->cur_scope, "RenderTargetView", true, true);
        }
    | KW_DEPTHSTENCILSTATE
        {
            $$ = hlsl_get_type(ctx->cur_scope, "DepthStencilState", true, true);
        }
    | KW_DEPTHSTENCILVIEW
        {
            $$ = hlsl_get_type(ctx->cur_scope, "DepthStencilView", true, true);
        }
    | KW_VERTEXSHADER
        {
            $$ = hlsl_get_type(ctx->cur_scope, "VertexShader", true, true);
        }
    | KW_PIXELSHADER
        {
            $$ = hlsl_get_type(ctx->cur_scope, "PixelShader", true, true);
        }
    | KW_COMPUTESHADER
        {
            $$ = hlsl_get_type(ctx->cur_scope, "ComputeShader", true, true);
        }
    | KW_DOMAINSHADER
        {
            $$ = hlsl_get_type(ctx->cur_scope, "DomainShader", true, true);
        }
    | KW_HULLSHADER
        {
            $$ = hlsl_get_type(ctx->cur_scope, "HullShader", true, true);
        }
    | KW_GEOMETRYSHADER
        {
            $$ = hlsl_get_type(ctx->cur_scope, "GeometryShader", true, true);
        }
    | KW_CONSTANTBUFFER '<' type '>'
        {
            if ($3->class != HLSL_CLASS_STRUCT)
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
                        "ConstantBuffer<...> requires user-defined structure type.");
            $$ = hlsl_new_cb_type(ctx, $3);
        }
    | KW_RASTERIZERSTATE
        {
            $$ = hlsl_get_type(ctx->cur_scope, "RasterizerState", true, true);
        }
    | KW_BLENDSTATE
        {
            $$ = hlsl_get_type(ctx->cur_scope, "BlendState", true, true);
        }

type:
      type_no_void
    | KW_VOID
        {
            $$ = ctx->builtin_types.Void;
        }

declaration_statement:
      declaration
    | struct_declaration_without_vars
        {
            if (!($$ = make_empty_block(ctx)))
                YYABORT;
        }
    | typedef
        {
            if (!($$ = make_empty_block(ctx)))
                YYABORT;
        }

typedef_type:
      type
    | struct_spec

typedef:
      KW_TYPEDEF var_modifiers typedef_type type_specs ';'
        {
            uint32_t modifiers = $2;
            struct hlsl_type *type;

            if (!(type = apply_type_modifiers(ctx, $3, &modifiers, false, &@2)))
            {
                destroy_parse_variable_defs($4);
                YYABORT;
            }

            if (modifiers)
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                        "Storage modifiers are not allowed on typedefs.");

            if (!add_typedef(ctx, type, $4))
                YYABORT;
        }

type_specs:
      type_spec
        {
            if (!($$ = make_empty_list(ctx)))
                YYABORT;
            list_add_head($$, &$1->entry);
        }
    | type_specs ',' type_spec
        {
            $$ = $1;
            list_add_tail($$, &$3->entry);
        }

type_spec:
      any_identifier arrays
        {
            $$ = hlsl_alloc(ctx, sizeof(*$$));
            $$->loc = @1;
            $$->name = $1;
            $$->arrays = $2;
        }

declaration:
      variables_def_typed ';'
        {
            if (!($$ = initialize_vars(ctx, $1)))
                YYABORT;
        }

variables_def:
      variable_def
        {
            if (!($$ = make_empty_list(ctx)))
                YYABORT;
            list_add_head($$, &$1->entry);
        }
    | variables_def ',' variable_def
        {
            $$ = $1;
            list_add_tail($$, &$3->entry);
        }

variables_def_typed:
      variable_def_typed
        {
            if (!($$ = make_empty_list(ctx)))
                YYABORT;
            list_add_head($$, &$1->entry);

            declare_var(ctx, $1);
        }
    | variables_def_typed ',' variable_def
        {
            struct parse_variable_def *head_def;

            VKD3D_ASSERT(!list_empty($1));
            head_def = LIST_ENTRY(list_head($1), struct parse_variable_def, entry);

            VKD3D_ASSERT(head_def->basic_type);
            $3->basic_type = head_def->basic_type;
            $3->modifiers = head_def->modifiers;
            $3->modifiers_loc = head_def->modifiers_loc;

            declare_var(ctx, $3);

            $$ = $1;
            list_add_tail($$, &$3->entry);
        }

variable_decl:
      any_identifier arrays colon_attributes annotations_opt
        {
            $$ = hlsl_alloc(ctx, sizeof(*$$));
            $$->loc = @1;
            $$->name = $1;
            $$->arrays = $2;
            $$->semantic = $3.semantic;
            $$->reg_reservation = $3.reg_reservation;
            $$->annotations = $4;
        }

state_block_start:
      %empty
        {
            ctx->in_state_block = 1;
        }

stateblock_lhs_identifier:
      any_identifier
        {
            $$ = $1;
        }
    | KW_PIXELSHADER
        {
            if (!($$ = hlsl_strdup(ctx, "pixelshader")))
                YYABORT;
        }
    | KW_TEXTURE
        {
            if (!($$ = hlsl_strdup(ctx, "texture")))
                YYABORT;
        }
    | KW_VERTEXSHADER
        {
            if (!($$ = hlsl_strdup(ctx, "vertexshader")))
                YYABORT;
        }
    | KW_GEOMETRYSHADER
        {
            if (!($$ = hlsl_strdup(ctx, "geometryshader")))
                YYABORT;
        }

state_block_index_opt:
      %empty
        {
            $$.has_index = false;
            $$.index = 0;
        }
    | '[' C_INTEGER ']'
       {
            if ($2 < 0)
            {
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_INDEX,
                        "State block array index is not a positive integer constant.");
                YYABORT;
            }
            $$.has_index = true;
            $$.index = $2;
       }

state_block:
      %empty
        {
            if (!($$ = hlsl_alloc(ctx, sizeof(*$$))))
                YYABORT;
        }
    | state_block stateblock_lhs_identifier state_block_index_opt '=' complex_initializer ';'
        {
            struct hlsl_state_block_entry *entry;
            unsigned int i;

            if (!(entry = hlsl_alloc(ctx, sizeof(*entry))))
                YYABORT;

            entry->name = $2;
            entry->lhs_has_index = $3.has_index;
            entry->lhs_index = $3.index;

            entry->instrs = $5.instrs;

            entry->args_count = $5.args_count;
            if (!(entry->args = hlsl_alloc(ctx, sizeof(*entry->args) * entry->args_count)))
                YYABORT;
            for (i = 0; i < entry->args_count; ++i)
                hlsl_src_from_node(&entry->args[i], $5.args[i]);
            vkd3d_free($5.args);

            $$ = $1;
            hlsl_state_block_add_entry($$, entry);
        }
    | state_block stateblock_lhs_identifier state_block_index_opt '=' '<' primary_expr '>' ';'
        {
            struct hlsl_state_block_entry *entry;

            if (!(entry = hlsl_alloc(ctx, sizeof(*entry))))
                YYABORT;

            entry->name = $2;
            entry->lhs_has_index = $3.has_index;
            entry->lhs_index = $3.index;

            entry->instrs = $6;
            entry->args_count = 1;
            if (!(entry->args = hlsl_alloc(ctx, sizeof(*entry->args) * entry->args_count)))
                YYABORT;
            hlsl_src_from_node(&entry->args[0], node_from_block($6));

            $$ = $1;
            hlsl_state_block_add_entry($$, entry);
        }
    | state_block any_identifier '(' func_arguments ')' ';'
        {
            struct hlsl_state_block_entry *entry;
            unsigned int i;

            if (!(entry = hlsl_alloc(ctx, sizeof(*entry))))
                YYABORT;

            entry->is_function_call = true;

            entry->name = $2;
            entry->lhs_has_index = false;
            entry->lhs_index = 0;

            entry->instrs = $4.instrs;

            entry->args_count = $4.args_count;
            if (!(entry->args = hlsl_alloc(ctx, sizeof(*entry->args) * entry->args_count)))
                YYABORT;
            for (i = 0; i < entry->args_count; ++i)
                hlsl_src_from_node(&entry->args[i], $4.args[i]);
            vkd3d_free($4.args);

            hlsl_validate_state_block_entry(ctx, entry, &@4);

            $$ = $1;
            hlsl_state_block_add_entry($$, entry);
        }

state_block_list:
      '{' state_block '}'
        {
            if (!($$ = hlsl_alloc(ctx, sizeof(*$$))))
                YYABORT;

            if(!(vkd3d_array_reserve((void **)&$$->state_blocks, &$$->state_block_capacity,
                    $$->state_block_count + 1, sizeof(*$$->state_blocks))))
                YYABORT;
            $$->state_blocks[$$->state_block_count++] = $2;
        }
    | state_block_list ',' '{' state_block '}'
        {
            $$ = $1;

            if(!(vkd3d_array_reserve((void **)&$$->state_blocks, &$$->state_block_capacity,
                    $$->state_block_count + 1, sizeof(*$$->state_blocks))))
                YYABORT;
            $$->state_blocks[$$->state_block_count++] = $4;
        }

variable_def:
      variable_decl
    | variable_decl '=' complex_initializer
        {
            $$ = $1;
            $$->initializer = $3;
        }
    | variable_decl '{' state_block_start state_block '}'
        {
            $$ = $1;
            ctx->in_state_block = 0;

            if(!(vkd3d_array_reserve((void **)&$$->state_blocks, &$$->state_block_capacity,
                    $$->state_block_count + 1, sizeof(*$$->state_blocks))))
                YYABORT;
            $$->state_blocks[$$->state_block_count++] = $4;
        }
    | variable_decl '{' state_block_start state_block_list '}'
        {
            $$ = $1;
            ctx->in_state_block = 0;

            $$->state_blocks = $4->state_blocks;
            $$->state_block_count = $4->state_block_count;
            $$->state_block_capacity = $4->state_block_capacity;
            $4->state_blocks = NULL;
            $4->state_block_count = 0;
            $4->state_block_capacity = 0;
            free_parse_variable_def($4);
        }

variable_def_typed:
      var_modifiers struct_spec variable_def
        {
            uint32_t modifiers = $1;
            struct hlsl_type *type;

            if (!(type = apply_type_modifiers(ctx, $2, &modifiers, true, &@1)))
                YYABORT;

            check_invalid_non_parameter_modifiers(ctx, modifiers, &@1);

            $$ = $3;
            $$->basic_type = type;
            $$->modifiers = modifiers;
            $$->modifiers_loc = @1;
        }
    | var_modifiers type variable_def
        {
            uint32_t modifiers = $1;
            struct hlsl_type *type;

            if (!(type = apply_type_modifiers(ctx, $2, &modifiers, true, &@1)))
                YYABORT;

            check_invalid_non_parameter_modifiers(ctx, modifiers, &@1);

            $$ = $3;
            $$->basic_type = type;
            $$->modifiers = modifiers;
            $$->modifiers_loc = @1;
        }

array:
      '[' ']'
        {
            $$ = HLSL_ARRAY_ELEMENTS_COUNT_IMPLICIT;
        }
    | '[' expr ']'
        {
            $$ = evaluate_static_expression_as_uint(ctx, $2, &@2);

            if (!$$)
            {
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_SIZE,
                        "Array size is not a positive integer constant.");
                YYABORT;
            }

            if ($$ > 65536)
            {
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_SIZE,
                        "Array size %u is not between 1 and 65536.", $$);
                YYABORT;
            }

            destroy_block($2);
        }

arrays:
      %empty
        {
            $$.sizes = NULL;
            $$.count = 0;
        }
    | array arrays
        {
            uint32_t *new_array;

            $$ = $2;

            if (!(new_array = hlsl_realloc(ctx, $$.sizes, ($$.count + 1) * sizeof(*new_array))))
            {
                vkd3d_free($$.sizes);
                YYABORT;
            }

            $$.sizes = new_array;
            $$.sizes[$$.count++] = $1;
        }

var_modifiers:
      %empty
        {
            $$ = 0;
        }
    | KW_EXTERN var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_EXTERN, &@1);
        }
    | KW_NOINTERPOLATION var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_NOINTERPOLATION, &@1);
        }
    | KW_CENTROID var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_CENTROID, &@1);
        }
    | KW_LINEAR var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_LINEAR, &@1);
        }
    | KW_NOPERSPECTIVE var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_NOPERSPECTIVE, &@1);
        }
    | KW_SHARED var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_SHARED, &@1);
        }
    | KW_GROUPSHARED var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_GROUPSHARED, &@1);
        }
    | KW_STATIC var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_STATIC, &@1);
        }
    | KW_UNIFORM var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_UNIFORM, &@1);
        }
    | KW_VOLATILE var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_VOLATILE, &@1);
        }
    | KW_CONST var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_CONST, &@1);
        }
    | KW_ROW_MAJOR var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_ROW_MAJOR, &@1);
        }
    | KW_COLUMN_MAJOR var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_COLUMN_MAJOR, &@1);
        }
    | KW_IN var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_IN, &@1);
        }
    | KW_OUT var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_OUT, &@1);
        }
    | KW_INOUT var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_STORAGE_IN | HLSL_STORAGE_OUT, &@1);
        }
    | KW_INLINE var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_INLINE, &@1);
        }
    | KW_EXPORT var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_EXPORT, &@1);
        }
    | KW_UNORM var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_UNORM, &@1);
        }
    | KW_SNORM var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_SNORM, &@1);
        }
    | KW_LINE var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_PRIMITIVE_LINE, &@1);
        }
    | KW_LINEADJ var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_PRIMITIVE_LINEADJ, &@1);
        }
    | KW_POINT var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_PRIMITIVE_POINT, &@1);
        }
    | KW_TRIANGLE var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_PRIMITIVE_TRIANGLE, &@1);
        }
    | KW_TRIANGLEADJ var_modifiers
        {
            $$ = add_modifiers(ctx, $2, HLSL_PRIMITIVE_TRIANGLEADJ, &@1);
        }
    | var_identifier var_modifiers
        {
            $$ = $2;

            if (!strcmp($1, "precise"))
                $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_PRECISE, &@1);
            else if (!strcmp($1, "single"))
                $$ = add_modifiers(ctx, $2, HLSL_MODIFIER_SINGLE, &@1);
            else
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_UNKNOWN_MODIFIER,
                        "Unknown modifier %s.", debugstr_a($1));
        }

complex_initializer:
      initializer_expr
        {
            $$.args_count = 1;
            if (!($$.args = hlsl_alloc(ctx, sizeof(*$$.args))))
            {
                destroy_block($1);
                YYABORT;
            }
            $$.args[0] = node_from_block($1);
            $$.instrs = $1;
            $$.braces = false;
            $$.loc = @$;
        }
    | '{' complex_initializer_list '}'
        {
            $$ = $2;
            $$.braces = true;
        }
    | '{' complex_initializer_list ',' '}'
        {
            $$ = $2;
            $$.braces = true;
        }

complex_initializer_list:
      complex_initializer
    | complex_initializer_list ',' complex_initializer
        {
            struct hlsl_ir_node **new_args;
            unsigned int i;

            $$ = $1;
            if (!(new_args = hlsl_realloc(ctx, $$.args, ($$.args_count + $3.args_count) * sizeof(*$$.args))))
            {
                free_parse_initializer(&$$);
                free_parse_initializer(&$3);
                YYABORT;
            }
            $$.args = new_args;
            for (i = 0; i < $3.args_count; ++i)
                $$.args[$$.args_count++] = $3.args[i];
            hlsl_block_add_block($$.instrs, $3.instrs);
            free_parse_initializer(&$3);
            $$.loc = @$;
        }

initializer_expr:
      assignment_expr

initializer_expr_list:
      initializer_expr
        {
            $$.args_count = 1;
            if (!($$.args = hlsl_alloc(ctx, sizeof(*$$.args))))
            {
                destroy_block($1);
                YYABORT;
            }
            $$.args[0] = node_from_block($1);
            $$.instrs = $1;
            $$.braces = false;
            $$.loc = @$;
        }
    | initializer_expr_list ',' initializer_expr
        {
            struct hlsl_ir_node **new_args;

            $$ = $1;
            if (!(new_args = hlsl_realloc(ctx, $$.args, ($$.args_count + 1) * sizeof(*$$.args))))
            {
                free_parse_initializer(&$$);
                destroy_block($3);
                YYABORT;
            }
            $$.args = new_args;
            $$.args[$$.args_count++] = node_from_block($3);
            hlsl_block_add_block($$.instrs, $3);
            destroy_block($3);
        }

boolean:
      KW_TRUE
        {
            $$ = true;
        }
    | KW_FALSE
        {
            $$ = false;
        }

statement_list:
      statement
    | statement_list statement
        {
            $$ = $1;
            hlsl_block_add_block($$, $2);
            destroy_block($2);
        }

statement:
      declaration_statement
    | expr_statement
    | scope_start compound_statement
        {
            hlsl_pop_scope(ctx);
            $$ = $2;
        }
    | jump_statement
    | selection_statement
    | loop_statement
    | switch_statement

jump_statement:
      KW_BREAK ';'
        {
            if (!is_break_allowed(ctx->cur_scope))
            {
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                        "The 'break' statement must be used inside of a loop or a switch.");
            }

            if (!($$ = make_empty_block(ctx)))
                YYABORT;
            hlsl_block_add_jump(ctx, $$, HLSL_IR_JUMP_BREAK, NULL, &@1);
        }
    | KW_CONTINUE ';'
        {
            check_continue(ctx, ctx->cur_scope, &@1);

            if (!($$ = make_empty_block(ctx)))
                YYABORT;
            hlsl_block_add_jump(ctx, $$, HLSL_IR_JUMP_UNRESOLVED_CONTINUE, NULL, &@1);
        }
    | KW_RETURN expr ';'
        {
            $$ = $2;
            if (!add_return(ctx, $$, node_from_block($$), &@1))
                YYABORT;
        }
    | KW_RETURN ';'
        {
            if (!($$ = make_empty_block(ctx)))
                YYABORT;
            if (!add_return(ctx, $$, NULL, &@1))
                YYABORT;
        }
    | KW_DISCARD ';'
        {
            struct hlsl_ir_node *c;

            if (!($$ = make_empty_block(ctx)))
                YYABORT;
            c = hlsl_block_add_uint_constant(ctx, $$, ~0u, &@1);
            hlsl_block_add_jump(ctx, $$, HLSL_IR_JUMP_DISCARD_NZ, c, &@1);
        }

selection_statement:
      attribute_list_optional KW_IF '(' expr ')' if_body
        {
            struct hlsl_ir_node *condition = node_from_block($4);
            const struct parse_attribute_list *attributes = &$1;
            enum hlsl_if_flatten_type flatten_type = HLSL_IF_FLATTEN_DEFAULT;
            unsigned int i;

            check_attribute_list_for_duplicates(ctx, attributes);

            for (i = 0; i < attributes->count; ++i)
            {
                const struct hlsl_attribute *attr = attributes->attrs[i];

                if (!strcmp(attr->name, "branch"))
                {
                    if (flatten_type == HLSL_IF_FORCE_FLATTEN)
                        hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                                "The 'branch' and 'flatten' attributes are mutually exclusive.");
                    flatten_type = HLSL_IF_FORCE_BRANCH;
                }
                else if (!strcmp(attr->name, "flatten"))
                {
                    if (flatten_type == HLSL_IF_FORCE_BRANCH)
                        hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX,
                                "The 'branch' and 'flatten' attributes are mutually exclusive.");
                    flatten_type = HLSL_IF_FORCE_FLATTEN;
                }
                else
                {
                    hlsl_warning(ctx, &@1, VKD3D_SHADER_WARNING_HLSL_UNKNOWN_ATTRIBUTE, "Unrecognized attribute '%s'.", attr->name);
                }
            }

            if (flatten_type == HLSL_IF_FORCE_BRANCH && hlsl_version_lt(ctx, 2, 1))
            {
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INCOMPATIBLE_PROFILE,
                        "The 'branch' attribute requires shader model 2.1 or higher.");
            }

            check_condition_type(ctx, condition);

            condition = add_cast(ctx, $4, condition, hlsl_get_scalar_type(ctx, HLSL_TYPE_BOOL), &@4);
            hlsl_block_add_if(ctx, $4, condition, $6.then_block, $6.else_block, flatten_type, &@2);

            destroy_block($6.then_block);
            destroy_block($6.else_block);
            cleanup_parse_attribute_list(&$1);

            $$ = $4;
        }

if_body:
      statement
        {
            $$.then_block = $1;
            $$.else_block = NULL;
        }
    | statement KW_ELSE statement
        {
            $$.then_block = $1;
            $$.else_block = $3;
        }

loop_statement:
      attribute_list_optional loop_scope_start KW_WHILE '(' expr ')' statement
        {
            $$ = create_loop(ctx, HLSL_LOOP_WHILE, &$1, NULL, $5, NULL, $7, &@3);
            hlsl_pop_scope(ctx);
            cleanup_parse_attribute_list(&$1);
        }
    | attribute_list_optional loop_scope_start KW_DO statement KW_WHILE '(' expr ')' ';'
        {
            $$ = create_loop(ctx, HLSL_LOOP_DO_WHILE, &$1, NULL, $7, NULL, $4, &@3);
            hlsl_pop_scope(ctx);
            cleanup_parse_attribute_list(&$1);
        }
    | attribute_list_optional loop_scope_start KW_FOR '(' expr_statement expr_statement expr_optional ')' statement
        {
            $$ = create_loop(ctx, HLSL_LOOP_FOR, &$1, $5, $6, $7, $9, &@3);
            hlsl_pop_scope(ctx);
            cleanup_parse_attribute_list(&$1);
        }
    | attribute_list_optional loop_scope_start KW_FOR '(' declaration expr_statement expr_optional ')' statement
        {
            $$ = create_loop(ctx, HLSL_LOOP_FOR, &$1, $5, $6, $7, $9, &@3);
            hlsl_pop_scope(ctx);
            cleanup_parse_attribute_list(&$1);
        }

switch_statement:
      attribute_list_optional switch_scope_start KW_SWITCH '(' expr ')' '{' switch_cases '}'
        {
            $$ = $5;
            if (!add_switch(ctx, $$, &$1, $8, &@3))
                YYABORT;
            hlsl_pop_scope(ctx);
        }

switch_case:
      KW_CASE expr ':' statement_list
        {
            struct hlsl_ir_switch_case *c;
            unsigned int value;

            value = evaluate_static_expression_as_uint(ctx, $2, &@2);

            c = hlsl_new_switch_case(ctx, value, false, $4, &@2);

            destroy_block($2);
            destroy_block($4);

            if (!c)
                YYABORT;
            $$ = c;
        }
    | KW_CASE expr ':'
        {
            struct hlsl_ir_switch_case *c;
            unsigned int value;

            value = evaluate_static_expression_as_uint(ctx, $2, &@2);

            c = hlsl_new_switch_case(ctx, value, false, NULL, &@2);

            destroy_block($2);

            if (!c)
                YYABORT;
            $$ = c;
        }
    | KW_DEFAULT ':' statement_list
        {
            struct hlsl_ir_switch_case *c;

            c = hlsl_new_switch_case(ctx, 0, true, $3, &@1);

            destroy_block($3);

            if (!c)
                YYABORT;
            $$ = c;
        }
    | KW_DEFAULT ':'
        {
            struct hlsl_ir_switch_case *c;

            if (!(c = hlsl_new_switch_case(ctx, 0, true, NULL, &@1)))
                YYABORT;
            $$ = c;
        }

switch_cases:
      switch_case
        {
            struct hlsl_ir_switch_case *c = LIST_ENTRY($1, struct hlsl_ir_switch_case, entry);
            if (!($$ = make_empty_list(ctx)))
            {
                hlsl_free_ir_switch_case(c);
                YYABORT;
            }
            list_add_head($$, &$1->entry);
        }
    | switch_cases switch_case
        {
            $$ = $1;
            check_duplicated_switch_cases(ctx, $2, $$);
            list_add_tail($$, &$2->entry);
        }

expr_optional:
      %empty
        {
            if (!($$ = make_empty_block(ctx)))
                YYABORT;
        }
    | expr

expr_statement:
      expr_optional ';'
        {
            $$ = $1;
        }

func_arguments:
      %empty
        {
            $$.args = NULL;
            $$.args_count = 0;
            if (!($$.instrs = make_empty_block(ctx)))
                YYABORT;
            $$.braces = false;
            $$.loc = @$;
        }
    | initializer_expr_list

primary_expr:
      C_FLOAT
        {
            if (!($$ = make_empty_block(ctx)))
                YYABORT;
            hlsl_block_add_float_constant(ctx, $$, $1, &@1);
        }
    | C_INTEGER
        {
            if (!($$ = make_empty_block(ctx)))
                YYABORT;
            hlsl_block_add_int_constant(ctx, $$, $1, &@1);
        }
    | C_UNSIGNED
        {
            if (!($$ = make_empty_block(ctx)))
                YYABORT;
            hlsl_block_add_uint_constant(ctx, $$, $1, &@1);
        }
    | boolean
        {
            struct hlsl_ir_node *c;

            if (!(c = hlsl_new_bool_constant(ctx, $1, &@1)))
                YYABORT;
            if (!($$ = make_block(ctx, c)))
            {
                hlsl_free_instr(c);
                YYABORT;
            }
        }
    | STRING
        {
            struct hlsl_ir_node *c;

            if (!(c = hlsl_new_string_constant(ctx, $1, &@1)))
            {
                vkd3d_free($1);
                YYABORT;
            }
            vkd3d_free($1);

            if (!($$ = make_block(ctx, c)))
            {
                hlsl_free_instr(c);
                YYABORT;
            }
        }
    | KW_NULL
        {
            struct hlsl_ir_node *c;

            if (!(c = hlsl_new_null_constant(ctx, &@1)))
                YYABORT;
            if (!($$ = make_block(ctx, c)))
            {
                hlsl_free_instr(c);
                YYABORT;
            }
        }
    | VAR_IDENTIFIER
        {
            struct hlsl_ir_var *var;

            if ((var = hlsl_get_var(ctx->cur_scope, $1)))
            {
                vkd3d_free($1);

                if (!($$ = make_empty_block(ctx)))
                    YYABORT;
                hlsl_block_add_simple_load(ctx, $$, var, &@1);
            }
            else
            {
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED, "Variable \"%s\" is not defined.", $1);
                vkd3d_free($1);

                if (!($$ = make_empty_block(ctx)))
                    YYABORT;
                $$->value = ctx->error_instr;
            }
        }
    | '(' expr ')'
        {
            $$ = $2;
        }

    | KW_COMPILE any_identifier var_identifier '(' func_arguments ')'
        {
            if (!($$ = add_shader_compilation(ctx, $2, $3, &$5, &@1)))
            {
                vkd3d_free($2);
                vkd3d_free($3);
                YYABORT;
            }
            vkd3d_free($2);
            vkd3d_free($3);
        }
    | KW_COMPILESHADER '(' any_identifier ',' var_identifier '(' func_arguments ')' ')'
        {
            if (!($$ = add_shader_compilation(ctx, $3, $5, &$7, &@1)))
            {
                vkd3d_free($3);
                vkd3d_free($5);
                YYABORT;
            }
            vkd3d_free($3);
            vkd3d_free($5);
        }
    | var_identifier '(' func_arguments ')'
        {
            $$ = add_call(ctx, $1, &$3, &@1);
            vkd3d_free($1);
        }
    | KW_SAMPLER_STATE '{' state_block_start state_block '}'
        {
            struct hlsl_ir_node *sampler_state;
            ctx->in_state_block = 0;

            if (!ctx->in_state_block && ctx->cur_scope != ctx->globals)
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_MISPLACED_SAMPLER_STATE,
                        "sampler_state must be in global scope or a state block.");

            if (!(sampler_state = hlsl_new_sampler_state(ctx, $4, &@1)))
            {
                hlsl_free_state_block($4);
                YYABORT;
            }
            hlsl_free_state_block($4);

            if (!($$ = make_block(ctx, sampler_state)))
                YYABORT;
        }
    | NEW_IDENTIFIER
        {
            if (ctx->in_state_block)
            {
                struct hlsl_ir_node *constant;

                if (!(constant = hlsl_new_stateblock_constant(ctx, $1, &@1)))
                    YYABORT;
                vkd3d_free($1);

                if (!($$ = make_block(ctx, constant)))
                    YYABORT;
            }
            else
            {
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED, "Identifier \"%s\" is not declared.", $1);
                vkd3d_free($1);

                if (!($$ = make_empty_block(ctx)))
                    YYABORT;
                $$->value = ctx->error_instr;
            }
        }

postfix_expr:
      primary_expr
    | postfix_expr OP_INC
        {
            if (!add_increment(ctx, $1, false, true, &@2))
            {
                destroy_block($1);
                YYABORT;
            }
            $$ = $1;
        }
    | postfix_expr OP_DEC
        {
            if (!add_increment(ctx, $1, true, true, &@2))
            {
                destroy_block($1);
                YYABORT;
            }
            $$ = $1;
        }
    | postfix_expr '.' any_identifier
        {
            struct hlsl_ir_node *node = node_from_block($1);

            if (node->data_type->class == HLSL_CLASS_STRUCT)
            {
                add_record_access_recurse(ctx, $1, $3, &@2);
            }
            else if (hlsl_is_numeric_type(node->data_type))
            {
                struct hlsl_ir_node *swizzle;

                if ((swizzle = get_swizzle(ctx, node, $3, &@3)))
                {
                    hlsl_block_add_instr($1, swizzle);
                }
                else
                {
                    hlsl_error(ctx, &@3, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX, "Invalid swizzle \"%s\".", $3);
                    $1->value = ctx->error_instr;
                }
            }
            else if (add_object_property_access(ctx, $1, node, $3, &@2))
            {
            }
            else if (node->data_type->class != HLSL_CLASS_ERROR)
            {
                hlsl_error(ctx, &@3, VKD3D_SHADER_ERROR_HLSL_INVALID_SYNTAX, "Invalid subscript \"%s\".", $3);
                $1->value = ctx->error_instr;
            }
            vkd3d_free($3);
            $$ = $1;
        }
    | postfix_expr '[' expr ']'
        {
            struct hlsl_ir_node *array = node_from_block($1), *index = node_from_block($3);

            hlsl_block_add_block($3, $1);
            destroy_block($1);

            if (!add_array_access(ctx, $3, array, index, &@2))
            {
                destroy_block($3);
                YYABORT;
            }
            $$ = $3;
        }

    /* var_modifiers is necessary to avoid shift/reduce conflicts. */
    | var_modifiers type '(' initializer_expr_list ')'
        {
            if ($1)
                hlsl_error(ctx, &@1, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                        "Modifiers are not allowed on constructors.");

            if (!($$ = add_constructor(ctx, $2, &$4, &@2)))
            {
                free_parse_initializer(&$4);
                YYABORT;
            }
        }
    | postfix_expr '.' any_identifier '(' func_arguments ')'
        {
            struct hlsl_ir_node *object = node_from_block($1);

            hlsl_block_add_block($1, $5.instrs);
            vkd3d_free($5.instrs);

            if (!add_method_call(ctx, $1, object, $3, &$5, &@3))
            {
                destroy_block($1);
                vkd3d_free($5.args);
                YYABORT;
            }
            vkd3d_free($5.args);
            $$ = $1;
        }

unary_expr:
      postfix_expr
    | OP_INC unary_expr
        {
            if (!add_increment(ctx, $2, false, false, &@1))
            {
                destroy_block($2);
                YYABORT;
            }
            $$ = $2;
        }
    | OP_DEC unary_expr
        {
            if (!add_increment(ctx, $2, true, false, &@1))
            {
                destroy_block($2);
                YYABORT;
            }
            $$ = $2;
        }
    | '+' unary_expr
        {
            $$ = $2;
        }
    | '-' unary_expr
        {
            add_unary_arithmetic_expr(ctx, $2, HLSL_OP1_NEG, node_from_block($2), &@1);
            $$ = $2;
        }
    | '~' unary_expr
        {
            add_unary_bitwise_expr(ctx, $2, HLSL_OP1_BIT_NOT, node_from_block($2), &@1);
            $$ = $2;
        }
    | '!' unary_expr
        {
            add_unary_logical_expr(ctx, $2, HLSL_OP1_LOGIC_NOT, node_from_block($2), &@1);
            $$ = $2;
        }
    /* var_modifiers is necessary to avoid shift/reduce conflicts. */
    | '(' var_modifiers type arrays ')' unary_expr
        {
            if ($2)
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                        "Modifiers are not allowed on casts.");

            add_explicit_conversion(ctx, $6, $3, &$4, &@3);
            vkd3d_free($4.sizes);
            $$ = $6;
        }

mul_expr:
      unary_expr
    | mul_expr '*' unary_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_MUL, &@2);
        }
    | mul_expr '/' unary_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_DIV, &@2);
        }
    | mul_expr '%' unary_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_MOD, &@2);
        }

add_expr:
      mul_expr
    | add_expr '+' mul_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_ADD, &@2);
        }
    | add_expr '-' mul_expr
        {
            struct hlsl_ir_node *neg;

            if (!(neg = add_unary_arithmetic_expr(ctx, $3, HLSL_OP1_NEG, node_from_block($3), &@2)))
                YYABORT;
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_ADD, &@2);
        }

shift_expr:
      add_expr
    | shift_expr OP_LEFTSHIFT add_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_LSHIFT, &@2);
        }
    | shift_expr OP_RIGHTSHIFT add_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_RSHIFT, &@2);
        }

relational_expr:
      shift_expr
    | relational_expr '<' shift_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_LESS, &@2);
        }
    | relational_expr '>' shift_expr
        {
            $$ = add_binary_expr_merge(ctx, $3, $1, HLSL_OP2_LESS, &@2);
        }
    | relational_expr OP_LE shift_expr
        {
            $$ = add_binary_expr_merge(ctx, $3, $1, HLSL_OP2_GEQUAL, &@2);
        }
    | relational_expr OP_GE shift_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_GEQUAL, &@2);
        }

equality_expr:
      relational_expr
    | equality_expr OP_EQ relational_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_EQUAL, &@2);
        }
    | equality_expr OP_NE relational_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_NEQUAL, &@2);
        }

bitand_expr:
      equality_expr
    | bitand_expr '&' equality_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_BIT_AND, &@2);
        }

bitxor_expr:
      bitand_expr
    | bitxor_expr '^' bitand_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_BIT_XOR, &@2);
        }

bitor_expr:
      bitxor_expr
    | bitor_expr '|' bitxor_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_BIT_OR, &@2);
        }

logicand_expr:
      bitor_expr
    | logicand_expr OP_AND bitor_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_LOGIC_AND, &@2);
        }

logicor_expr:
      logicand_expr
    | logicor_expr OP_OR logicand_expr
        {
            $$ = add_binary_expr_merge(ctx, $1, $3, HLSL_OP2_LOGIC_OR, &@2);
        }

conditional_expr:
      logicor_expr
    | logicor_expr '?' expr ':' assignment_expr
        {
            struct hlsl_ir_node *cond = node_from_block($1);
            struct hlsl_ir_node *first = node_from_block($3);
            struct hlsl_ir_node *second = node_from_block($5);

            hlsl_block_add_block($1, $3);
            hlsl_block_add_block($1, $5);
            destroy_block($3);
            destroy_block($5);

            if (!add_ternary(ctx, $1, cond, first, second))
                YYABORT;
            $$ = $1;
        }

assignment_expr:

      conditional_expr
    | unary_expr assign_op assignment_expr
        {
            struct hlsl_ir_node *lhs = node_from_block($1), *rhs = node_from_block($3);

            if (lhs->data_type->modifiers & HLSL_MODIFIER_CONST)
                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_MODIFIES_CONST, "Statement modifies a const expression.");
            hlsl_block_add_block($3, $1);
            destroy_block($1);
            if (!add_assignment(ctx, $3, lhs, $2, rhs, false))
                YYABORT;
            $$ = $3;
        }

assign_op:
      '='
        {
            $$ = ASSIGN_OP_ASSIGN;
        }
    | OP_ADDASSIGN
        {
            $$ = ASSIGN_OP_ADD;
        }
    | OP_SUBASSIGN
        {
            $$ = ASSIGN_OP_SUB;
        }
    | OP_MULASSIGN
        {
            $$ = ASSIGN_OP_MUL;
        }
    | OP_DIVASSIGN
        {
            $$ = ASSIGN_OP_DIV;
        }
    | OP_MODASSIGN
        {
            $$ = ASSIGN_OP_MOD;
        }
    | OP_LEFTSHIFTASSIGN
        {
            $$ = ASSIGN_OP_LSHIFT;
        }
    | OP_RIGHTSHIFTASSIGN
        {
            $$ = ASSIGN_OP_RSHIFT;
        }
    | OP_ANDASSIGN
        {
            $$ = ASSIGN_OP_AND;
        }
    | OP_ORASSIGN
        {
            $$ = ASSIGN_OP_OR;
        }
    | OP_XORASSIGN
        {
            $$ = ASSIGN_OP_XOR;
        }

expr:
      assignment_expr
    | expr ',' assignment_expr
        {
            $$ = $1;
            hlsl_block_add_block($$, $3);
            destroy_block($3);
        }
