// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/tint/lang/core/ir/transform/vertex_pulling.h"

#include <utility>

#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/validator.h"
#include "src/tint/lang/core/type/manager.h"

namespace tint::core::ir::transform {
namespace {

using namespace tint::core::fluent_types;     // NOLINT
using namespace tint::core::number_suffixes;  // NOLINT

/// @returns the vector width of @p format
uint32_t FormatWidth(VertexFormat format) {
    switch (format) {
        case VertexFormat::kUint8:
        case VertexFormat::kSint8:
        case VertexFormat::kUnorm8:
        case VertexFormat::kSnorm8:
        case VertexFormat::kUint16:
        case VertexFormat::kSint16:
        case VertexFormat::kUnorm16:
        case VertexFormat::kSnorm16:
        case VertexFormat::kFloat16:
        case VertexFormat::kFloat32:
        case VertexFormat::kUint32:
        case VertexFormat::kSint32:
            return 1;
        case VertexFormat::kUint8x2:
        case VertexFormat::kSint8x2:
        case VertexFormat::kUnorm8x2:
        case VertexFormat::kSnorm8x2:
        case VertexFormat::kUint16x2:
        case VertexFormat::kSint16x2:
        case VertexFormat::kUnorm16x2:
        case VertexFormat::kSnorm16x2:
        case VertexFormat::kFloat16x2:
        case VertexFormat::kFloat32x2:
        case VertexFormat::kUint32x2:
        case VertexFormat::kSint32x2:
            return 2;
        case VertexFormat::kFloat32x3:
        case VertexFormat::kUint32x3:
        case VertexFormat::kSint32x3:
            return 3;
        case VertexFormat::kUint8x4:
        case VertexFormat::kSint8x4:
        case VertexFormat::kUnorm8x4:
        case VertexFormat::kSnorm8x4:
        case VertexFormat::kUint16x4:
        case VertexFormat::kSint16x4:
        case VertexFormat::kUnorm16x4:
        case VertexFormat::kSnorm16x4:
        case VertexFormat::kFloat16x4:
        case VertexFormat::kFloat32x4:
        case VertexFormat::kUint32x4:
        case VertexFormat::kSint32x4:
        case VertexFormat::kUnorm10_10_10_2:
        case VertexFormat::kUnorm8x4BGRA:
            return 4;
    }
    TINT_UNREACHABLE();
}

/// PIMPL state for the transform.
struct State {
    /// The vertex pulling configuration.
    const VertexPullingConfig& config;

    /// The IR module.
    core::ir::Module& ir;

    /// The IR builder.
    core::ir::Builder b{ir};

    /// The type manager.
    core::type::Manager& ty{ir.Types()};

    /// A map from location to a descriptor that holds the format, buffer, the offset of the
    /// vertex data from the start of the buffer, and the offset of the attribute from the start of
    /// the element.
    struct LocationInfo {
        VertexFormat format;
        core::ir::Value* buffer = nullptr;
        // Word offset within the buffer to the start of the data for this invocation.
        core::ir::Value* base_offset = nullptr;
        // Byte offset within the data for this invocation to the start of the attribute data.
        uint32_t attr_byte_offset;
    };
    Hashmap<uint32_t, LocationInfo, 4> locations_{};

    /// The vertex index function parameter.
    core::ir::FunctionParam* vertex_index_ = nullptr;
    /// The instance index function parameter.
    core::ir::FunctionParam* instance_index_ = nullptr;

    /// Process the module.
    void Process() {
        // Find the vertex shader entry point. There should be at most one.
        core::ir::Function* ep = nullptr;
        for (auto& func : ir.functions) {
            if (func->IsVertex()) {
                TINT_IR_ASSERT(ir, !ep);
                ep = func;
            }
        }
        if (!ep) {
            return;
        }

        Vector<core::ir::FunctionParam*, 4> new_params;
        b.InsertBefore(ep->Block()->Front(), [&] {  //
            // Create the storage buffers and record vertex attribute information.
            CreateBuffers();

            // Pull vertex attributes out of the entry point parameters and replace them.
            for (auto* param : ep->Params()) {
                if (auto* str = param->Type()->As<core::type::Struct>()) {
                    ProcessStructParameter(param, str);
                    param->Destroy();
                } else if (auto loc = param->Attributes().location) {
                    // Load the vertex attribute and replace uses of the parameter with it.
                    auto* input = Load(*loc, param->Type());
                    param->ReplaceAllUsesWith(input);
                    param->Destroy();
                } else {
                    // Other parameters should be builtins, which can only be the vertex and
                    // instance indices. Replace any user-declared indices with the ones that we
                    // created when setting up the buffers.
                    auto builtin = param->Builtin();
                    TINT_IR_ASSERT(ir, builtin);
                    switch (*builtin) {
                        case core::BuiltinValue::kVertexIndex:
                            if (vertex_index_) {
                                param->ReplaceAllUsesWith(vertex_index_);
                                param->Destroy();
                            } else {
                                new_params.Push(param);
                            }
                            break;
                        case core::BuiltinValue::kInstanceIndex:
                            if (instance_index_) {
                                param->ReplaceAllUsesWith(instance_index_);
                                param->Destroy();
                            } else {
                                new_params.Push(param);
                            }
                            break;
                        default:
                            TINT_IR_UNREACHABLE(ir);
                    }
                }
            }
        });

        // Update the entry point with the new parameter list.
        if (vertex_index_) {
            new_params.Push(vertex_index_);
        }
        if (instance_index_) {
            new_params.Push(instance_index_);
        }
        ep->SetParams(std::move(new_params));
    }

    /// @returns the vertex index parameter, creating one if needed
    core::ir::FunctionParam* GetVertexIndex() {
        if (!vertex_index_) {
            vertex_index_ = b.FunctionParam<u32>("tint_vertex_index");
            vertex_index_->SetBuiltin(core::BuiltinValue::kVertexIndex);
        }
        return vertex_index_;
    }

    /// @returns the instance index parameter, creating one if needed
    core::ir::FunctionParam* GetInstanceIndex() {
        if (!instance_index_) {
            instance_index_ = b.FunctionParam<u32>("tint_instance_index");
            instance_index_->SetBuiltin(core::BuiltinValue::kInstanceIndex);
        }
        return instance_index_;
    }

    /// Create storage buffers and record vertex attribute information.
    /// Record instructions that need vertex and instance indices.
    void CreateBuffers() {
        for (uint32_t i = 0; i < config.vertex_state.size(); i++) {
            // Create the storage buffer.
            auto& buffer = config.vertex_state[i];
            auto buffer_name = "tint_vertex_buffer_" + std::to_string(i);
            core::ir::Var* var = nullptr;
            b.Append(ir.root_block, [&] {
                var = b.Var(buffer_name, ty.ptr<storage, array<u32>, read>());
                var->SetBindingPoint(config.pulling_group, i);
            });

            // Determine the base offset of the vertex data in the storage buffer.
            core::ir::Value* index = nullptr;
            if (buffer.step_mode == VertexStepMode::kVertex) {
                index = GetVertexIndex();
            } else if (buffer.step_mode == VertexStepMode::kInstance) {
                index = GetInstanceIndex();
            }
            if (buffer.array_stride != 4) {
                // Multiply the index by the stride in words.
                TINT_IR_ASSERT(ir, (buffer.array_stride & 3u) == 0u);
                index = b.Multiply(index, u32(buffer.array_stride / 4))->Result();
                ir.SetName(index, buffer_name + "_base");
            }

            // Register the format, buffer, and offset for each location slot.
            for (auto& attr : buffer.attributes) {
                locations_.Add(attr.shader_location, LocationInfo{
                                                         .format = attr.format,
                                                         .buffer = var->Result(),
                                                         .base_offset = index,
                                                         .attr_byte_offset = attr.offset,
                                                     });
            }
        }
    }

    /// Pull vertex attributes out of structure parameter.
    /// Replace the parameter with a new structure created from the attributes.
    /// @param param the parameter
    /// @param str the structure type
    void ProcessStructParameter(core::ir::FunctionParam* param, const core::type::Struct* str) {
        Vector<core::ir::Value*, 4> construct_args;
        for (auto* member : str->Members()) {
            if (auto loc = member->Attributes().location) {
                construct_args.Push(Load(*loc, member->Type()));
            } else {
                // Other parameters should be builtins, which can only be the vertex and instance
                // indices. Use the separate parameters that we created for these indices. Because
                // there are no duplicates, this doesn't conflict with the param handling above.
                TINT_IR_ASSERT(ir, member->Attributes().builtin);
                switch (*member->Attributes().builtin) {
                    case core::BuiltinValue::kVertexIndex:
                        construct_args.Push(GetVertexIndex());
                        break;
                    case core::BuiltinValue::kInstanceIndex:
                        construct_args.Push(GetInstanceIndex());
                        break;
                    default:
                        TINT_IR_UNREACHABLE(ir);
                }
            }
        }
        param->ReplaceAllUsesWith(b.Construct(str, std::move(construct_args))->Result());
    }

    /// Load a vertex attribute.
    /// @param location the location index of the attribute
    /// @param shader_type the type of the attribute declared in the shader
    /// @returns the loaded attribute value
    core::ir::Value* Load(uint32_t location, const core::type::Type* shader_type) {
        auto info = locations_.Get(location);
        TINT_IR_ASSERT(ir, info);

        // Load the attribute data from the buffer.
        auto* value = LoadFromBuffer(*info, shader_type->DeepestElement());

        // The shader type may have a different component width to the vertex attribute, so we may
        // need to add or remove components.
        auto src_width = FormatWidth(info->format);
        auto dst_width = 1u;
        if (auto* vec = shader_type->As<core::type::Vector>()) {
            dst_width = vec->Width();
        }
        if (dst_width < src_width) {
            // The type declared in the shader is narrower than the vertex attribute format, so
            // truncate the value with a swizzle.
            switch (dst_width) {
                case 1:
                    value = b.Swizzle(shader_type, value, Vector{0u})->Result();
                    break;
                case 2:
                    value = b.Swizzle(shader_type, value, Vector{0u, 1u})->Result();
                    break;
                case 3:
                    value = b.Swizzle(shader_type, value, Vector{0u, 1u, 2u})->Result();
                    break;
                default:
                    TINT_IR_UNREACHABLE(ir) << dst_width;
            }
        } else if (dst_width > src_width) {
            // The type declared in the shader is wider than the vertex attribute format, so append
            // values to pad it out. Append a `1` value for the fourth element of a vector,
            // otherwise append zero.
            auto* elem_ty = shader_type->DeepestElement();
            auto one = [&] {
                return tint::Switch(
                    elem_ty,                                                  //
                    [&](const core::type::I32*) { return b.Constant(1_i); },  //
                    [&](const core::type::U32*) { return b.Constant(1_u); },  //
                    [&](const core::type::F32*) { return b.Constant(1_f); },  //
                    [&](const core::type::F16*) { return b.Constant(1_h); },  //
                    TINT_ICE_ON_NO_MATCH);
            };
            Vector<core::ir::Value*, 4> values{value};
            for (uint32_t i = src_width; i < dst_width; i++) {
                values.Push(i == 3 ? one() : b.Zero(elem_ty));
            }
            value = b.Construct(shader_type, std::move(values))->Result();
        }

        return value;
    }

    /// Load attribute data from a buffer.
    /// @param info the location descriptor
    /// @param shader_element_type the element type of the attribute declared in the shader
    /// @returns the loaded attribute data
    core::ir::Value* LoadFromBuffer(const LocationInfo& info,
                                    const core::type::Type* shader_element_type) {
        // Helper for loading a single word from the buffer at an offset.
        auto load_u32 = [&](uint32_t offset) {
            auto offset_value = info.base_offset;
            offset += (info.attr_byte_offset / 4u);
            if (offset > 0) {
                offset_value = b.Add(offset_value, u32(offset))->Result();
            }
            auto* word =
                b.Load(b.Access<ptr<storage, u32, read>>(info.buffer, offset_value))->Result();
            // If the offset is not 4-byte aligned, shift the word so that the requested data starts
            // at the first byte. The shift amount is the offset of the byte within a word
            // multiplied by 8 to get the bit offset.
            if (info.attr_byte_offset & 3) {
                word = b.ShiftRight(word, u32((info.attr_byte_offset & 3) * 8))->Result();
            }
            return word;
        };
        // Helpers for loading non-u32 data from the buffer.
        auto load_i32 = [&](uint32_t offset) { return b.Bitcast<i32>(load_u32(offset))->Result(); };
        auto load_f32 = [&](uint32_t offset) { return b.Bitcast<f32>(load_u32(offset))->Result(); };
        auto load_ivec = [&](uint32_t offset, uint32_t bits, const core::type::Vector* vec) {
            // For a vec2u, we read the `xxxx'yyyy` u32 word. We then splat to a vec2 and left
            // shift so we have `(xxxx'yyyy, yyyy'xxxx)`. Finally, we right shift to produce
            // `(0000'xxxx, 0000'yyyy)`
            auto* uvec = ty.MatchWidth(ty.u32(), vec);
            // yyyyxxxx
            auto* word = load_u32(offset);
            if (vec->Type()->Is<core::type::I32>()) {
                word = b.Bitcast<i32>(word)->Result();
            }
            // yyyyxxxx, yyyyxxxx
            auto* splat = b.Construct(vec, word);
            // xxxxyyyy, yyyyxxxx
            core::ir::Instruction* shift_left = nullptr;
            switch (vec->Width()) {
                case 2:
                    if (bits == 8) {
                        shift_left = b.ShiftLeft(splat, b.Composite(uvec, 24_u, 16_u));
                    } else if (bits == 16) {
                        shift_left = b.ShiftLeft(splat, b.Composite(uvec, 16_u, 0_u));
                    } else {
                        TINT_IR_UNREACHABLE(ir);
                    }
                    break;
                case 4:
                    TINT_IR_ASSERT(ir, bits == 8);
                    shift_left = b.ShiftLeft(splat, b.Composite(uvec, 24_u, 16_u, 8_u, 0_u));
                    break;
                default:
                    TINT_IR_UNREACHABLE(ir);
            }
            // 0000xxxx, 0000yyyy
            return b.ShiftRight(shift_left, b.Splat(uvec, u32(32 - bits)))->Result();
        };
        // Helper to convert a value to f16 if required by the shader, otherwise returns the f32.
        auto float_value = [&](core::ir::Value* value) -> core::ir::Value* {
            // If the shader expects an f16 value, convert the value.
            if (shader_element_type->Is<core::type::F16>()) {
                return b.Convert(ty.MatchWidth(ty.f16(), value->Type()), value)->Result();
            }
            return value;
        };

        switch (info.format) {
            // Formats that are always u32 in the shader (or vectors of u32).
            // Shift/mask values to expand to 32-bits.
            case VertexFormat::kUint8:
                return b.And(load_u32(0), 0xFF_u)->Result();
            case VertexFormat::kUint8x2:
                return load_ivec(0, 8, ty.vec2u());
            case VertexFormat::kUint8x4:
                return load_ivec(0, 8, ty.vec4u());
            case VertexFormat::kUint16:
                return b.And(load_u32(0), 0xFFFF_u)->Result();
            case VertexFormat::kUint16x2:
                return load_ivec(0, 16, ty.vec2u());
            case VertexFormat::kUint16x4: {
                auto* xy = load_ivec(0, 16, ty.vec2u());
                auto* zw = load_ivec(1, 16, ty.vec2u());
                return b.Construct<vec4u>(xy, zw)->Result();
            }
            case VertexFormat::kUint32:
                return load_u32(0);
            case VertexFormat::kUint32x2: {
                auto* x = load_u32(0);
                auto* y = load_u32(1);
                return b.Construct<vec2u>(x, y)->Result();
            }
            case VertexFormat::kUint32x3: {
                auto* x = load_u32(0);
                auto* y = load_u32(1);
                auto* z = load_u32(2);
                return b.Construct<vec3u>(x, y, z)->Result();
            }
            case VertexFormat::kUint32x4: {
                auto* x = load_u32(0);
                auto* y = load_u32(1);
                auto* z = load_u32(2);
                auto* w = load_u32(3);
                return b.Construct<vec4u>(x, y, z, w)->Result();
            }

            // Formats that are always i32 in the shader (or vectors of i32).
            // Shift values to expand to 32-bits.
            case VertexFormat::kSint8: {
                // ******xx
                auto* word = b.Bitcast<i32>(load_u32(0));
                // 000000xx
                return b.ShiftRight(b.ShiftLeft(word, 24_u), 24_u)->Result();
            }
            case VertexFormat::kSint8x2:
                return load_ivec(0, 8, ty.vec2i());
            case VertexFormat::kSint8x4:
                return load_ivec(0, 8, ty.vec4i());
            case VertexFormat::kSint16: {
                // ****xxxx
                auto* word = b.Bitcast<i32>(load_u32(0));
                // 0000xxxx
                return b.ShiftRight(b.ShiftLeft(word, 16_u), 16_u)->Result();
            }
            case VertexFormat::kSint16x2:
                return load_ivec(0, 16, ty.vec2i());
            case VertexFormat::kSint16x4: {
                auto* xy = load_ivec(0, 16, ty.vec2i());
                auto* zw = load_ivec(1, 16, ty.vec2i());
                return b.Construct<vec4i>(xy, zw)->Result();
            }
            case VertexFormat::kSint32:
                return load_i32(0);
            case VertexFormat::kSint32x2: {
                auto* x = load_i32(0);
                auto* y = load_i32(1);
                return b.Construct<vec2i>(x, y)->Result();
            }
            case VertexFormat::kSint32x3: {
                auto* x = load_i32(0);
                auto* y = load_i32(1);
                auto* z = load_i32(2);
                return b.Construct<vec3i>(x, y, z)->Result();
            }
            case VertexFormat::kSint32x4: {
                auto* x = load_i32(0);
                auto* y = load_i32(1);
                auto* z = load_i32(2);
                auto* w = load_i32(3);
                return b.Construct<vec4i>(x, y, z, w)->Result();
            }

            // Unsigned normalized formats.
            // Use unpack builtins to convert to f32.
            case VertexFormat::kUnorm8: {
                // ******xx
                auto* word = load_u32(0);
                // 000000xx, ********, ********, ********
                auto* unpack = b.Call<vec4f>(core::BuiltinFn::kUnpack4X8Unorm, word);
                // 000000xx
                return float_value(b.Access<f32>(unpack, 0_u)->Result());
            }
            case VertexFormat::kUnorm8x2: {
                // ****yyxx
                auto* word = load_u32(0);
                // 000000xx, 000000yy, ********, ********
                auto* unpack = b.Call<vec4f>(core::BuiltinFn::kUnpack4X8Unorm, word);
                // 000000xx, 000000yy
                return float_value(b.Swizzle<vec2f>(unpack, Vector{0u, 1u})->Result());
            }
            case VertexFormat::kUnorm8x4: {
                // wwzzyyxx
                auto* word = load_u32(0);
                // 000000xx, 000000yy, 000000zz, 000000ww
                auto* unpack = b.Call<vec4f>(core::BuiltinFn::kUnpack4X8Unorm, word);
                return float_value(unpack->Result());
            }
            case VertexFormat::kUnorm8x4BGRA: {
                // wwzzyyxx
                auto* word = load_u32(0);
                // 000000xx, 000000yy, 000000zz, 000000ww
                auto* unpack = b.Call<vec4f>(core::BuiltinFn::kUnpack4X8Unorm, word);
                // 000000zz, 000000yy, 000000xx, 000000ww
                return float_value(b.Swizzle<vec4f>(unpack, Vector{2u, 1u, 0u, 3u})->Result());
            }
            case VertexFormat::kUnorm16: {
                // ****xxxx
                auto* word = load_u32(0);
                // 0000xxxx, ********
                auto* unpack = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Unorm, word);
                // 0000xxxx
                return float_value(b.Access<f32>(unpack, 0_u)->Result());
            }
            case VertexFormat::kUnorm16x2: {
                // yyyyxxxx
                auto* word = load_u32(0);
                // 0000xxxx, 0000yyyy
                auto* unpack = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Unorm, word);
                return float_value(unpack->Result());
            }
            case VertexFormat::kUnorm16x4: {
                // yyyyxxxx, wwwwzzzz
                auto* word0 = load_u32(0);
                auto* word1 = load_u32(1);
                // 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww
                auto* unpack0 = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Unorm, word0);
                auto* unpack1 = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Unorm, word1);
                return float_value(b.Construct<vec4f>(unpack0, unpack1)->Result());
            }

            // Signed normalized formats.
            // Use unpack builtins to expand to f32.
            case VertexFormat::kSnorm8: {
                // ******xx
                auto* word = load_u32(0);
                // 000000xx, ********, ********, ********
                auto* unpack = b.Call<vec4f>(core::BuiltinFn::kUnpack4X8Snorm, word);
                // 000000xx
                return float_value(b.Access<f32>(unpack, 0_u)->Result());
            }
            case VertexFormat::kSnorm8x2: {
                // ****yyxx
                auto* word = load_u32(0);
                // 000000xx, 000000yy, ********, ********
                auto* unpack = b.Call<vec4f>(core::BuiltinFn::kUnpack4X8Snorm, word);
                // 000000xx, 000000yy
                return float_value(b.Swizzle<vec2f>(unpack, Vector{0u, 1u})->Result());
            }
            case VertexFormat::kSnorm8x4: {
                // wwzzyyxx
                auto* word = load_u32(0);
                // 000000xx, 000000yy, 000000zz, 000000ww
                auto* unpack = b.Call<vec4f>(core::BuiltinFn::kUnpack4X8Snorm, word);
                return float_value(unpack->Result());
            }
            case VertexFormat::kSnorm16: {
                // ****xxxx
                auto* word = load_u32(0);
                // 0000xxxx, ********
                auto* unpack = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Snorm, word);
                // 0000xxxx
                return float_value(b.Access<f32>(unpack, 0_u)->Result());
            }
            case VertexFormat::kSnorm16x2: {
                // yyyyxxxx
                auto* word = load_u32(0);
                // 0000xxxx, 0000yyyy
                auto* unpack = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Snorm, word);
                return float_value(unpack->Result());
            }
            case VertexFormat::kSnorm16x4: {
                // yyyyxxxx, wwwwzzzz
                auto* word0 = load_u32(0);
                auto* word1 = load_u32(1);
                // 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww
                auto* unpack0 = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Snorm, word0);
                auto* unpack1 = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Snorm, word1);
                return float_value(b.Construct<vec4f>(unpack0, unpack1)->Result());
            }

            // F16 formats that can either be f16 or f32 in the shader.
            // If f16 is expected just bitcast, otherwise use unpack builtins to convert to f32.
            case VertexFormat::kFloat16: {
                // ****xxxx
                auto* word = load_u32(0);
                if (shader_element_type->Is<core::type::F16>()) {
                    // xxxx, ****
                    auto* bitcast = b.Bitcast<vec2h>(word);
                    // xxxx
                    return b.Access<f16>(bitcast, 0_u)->Result();
                } else {
                    // 0000xxxx, ********
                    auto* unpack = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Float, word);
                    // 0000xxxx
                    return b.Access<f32>(unpack, 0_u)->Result();
                }
            }
            case VertexFormat::kFloat16x2: {
                // yyyyxxxx
                auto* word = load_u32(0);
                if (shader_element_type->Is<core::type::F16>()) {
                    // xxxx, yyyy
                    return b.Bitcast<vec2h>(word)->Result();
                } else {
                    // 0000xxxx, 0000yyyy
                    auto* unpack = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Float, word);
                    return unpack->Result();
                }
            }
            case VertexFormat::kFloat16x4: {
                // yyyyxxxx, wwwwzzzz
                auto* word0 = load_u32(0);
                auto* word1 = load_u32(1);
                if (shader_element_type->Is<core::type::F16>()) {
                    // xxxx, yyyy, zzzz, wwww
                    auto* bitcast0 = b.Bitcast<vec2h>(word0);
                    auto* bitcast1 = b.Bitcast<vec2h>(word1);
                    return b.Construct<vec4h>(bitcast0, bitcast1)->Result();
                } else {
                    // 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww
                    auto* unpack0 = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Float, word0);
                    auto* unpack1 = b.Call<vec2f>(core::BuiltinFn::kUnpack2X16Float, word1);
                    return b.Construct<vec4f>(unpack0, unpack1)->Result();
                }
            }

            // F32 formats that can either be f16 or f32 in the shader.
            // Load the f32 data and downconvert to f16 if needed.
            case VertexFormat::kFloat32:
                return float_value(load_f32(0));
            case VertexFormat::kFloat32x2: {
                auto* x = load_f32(0);
                auto* y = load_f32(1);
                return float_value(b.Construct<vec2f>(x, y)->Result());
            }
            case VertexFormat::kFloat32x3: {
                auto* x = load_f32(0);
                auto* y = load_f32(1);
                auto* z = load_f32(2);
                return float_value(b.Construct<vec3f>(x, y, z)->Result());
            }
            case VertexFormat::kFloat32x4: {
                auto* x = load_f32(0);
                auto* y = load_f32(1);
                auto* z = load_f32(2);
                auto* w = load_f32(3);
                return float_value(b.Construct<vec4f>(x, y, z, w)->Result());
            }

            // Miscellaneous other formats that need custom handling.
            case VertexFormat::kUnorm10_10_10_2: {
                auto* u32s = b.Construct<vec4u>(load_u32(0));
                // shr = u32s >> vec4u(0, 10, 20, 30);
                auto* shr = b.ShiftRight(u32s, b.Composite<vec4u>(0_u, 10_u, 20_u, 30_u));
                // mask = shr & vec4u(0x3FF, 0x3FF, 0x3FF, 0x3);
                auto* mask = b.And(shr, b.Composite<vec4u>(0x3FF_u, 0x3FF_u, 0x3FF_u, 0x3_u));
                // vec4f(mask) / vec4f(1023, 1023, 1023, 3);
                auto* div = b.Composite<vec4f>(1023_f, 1023_f, 1023_f, 3_f);
                return float_value(b.Divide(b.Convert<vec4f>(mask), div)->Result());
            }
        }
        TINT_IR_UNREACHABLE(ir);
    }
};

}  // namespace

Result<SuccessType> VertexPulling(core::ir::Module& ir, const VertexPullingConfig& config) {
    TINT_CHECK_RESULT(ValidateBeforeIfNeeded(ir, kVertexPullingCapabilities, "core.VertexPulling"));

    State{config, ir}.Process();

    return Success;
}

}  // namespace tint::core::ir::transform
