/*
 * Copyright (C) 2019 Apple Inc. All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "config.h"
#include "WHLSLEntryPointScaffolding.h"

#if ENABLE(WEBGPU)

#include "WHLSLBuiltInSemantic.h"
#include "WHLSLFunctionDefinition.h"
#include "WHLSLGatherEntryPointItems.h"
#include "WHLSLPipelineDescriptor.h"
#include "WHLSLReferenceType.h"
#include "WHLSLResourceSemantic.h"
#include "WHLSLStageInOutSemantic.h"
#include "WHLSLStructureDefinition.h"
#include "WHLSLTypeNamer.h"
#include <algorithm>
#include <wtf/Optional.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringConcatenateNumbers.h>

namespace WebCore {

namespace WHLSL {

namespace Metal {

static String attributeForSemantic(AST::BuiltInSemantic& builtInSemantic)
{
    switch (builtInSemantic.variable()) {
    case AST::BuiltInSemantic::Variable::SVInstanceID:
        return "[[instance_id]]"_str;
    case AST::BuiltInSemantic::Variable::SVVertexID:
        return "[[vertex_id]]"_str;
    case AST::BuiltInSemantic::Variable::PSize:
        return "[[point_size]]"_str;
    case AST::BuiltInSemantic::Variable::SVPosition:
        return "[[position]]"_str;
    case AST::BuiltInSemantic::Variable::SVIsFrontFace:
        return "[[front_facing]]"_str;
    case AST::BuiltInSemantic::Variable::SVSampleIndex:
        return "[[sample_id]]"_str;
    case AST::BuiltInSemantic::Variable::SVInnerCoverage:
        return "[[sample_mask]]"_str;
    case AST::BuiltInSemantic::Variable::SVTarget:
        return makeString("[[color(", *builtInSemantic.targetIndex(), ")]]");
    case AST::BuiltInSemantic::Variable::SVDepth:
        return "[[depth(any)]]"_str;
    case AST::BuiltInSemantic::Variable::SVCoverage:
        return "[[sample_mask]]"_str;
    case AST::BuiltInSemantic::Variable::SVDispatchThreadID:
        return "[[thread_position_in_grid]]"_str;
    case AST::BuiltInSemantic::Variable::SVGroupID:
        return "[[threadgroup_position_in_grid]]"_str;
    case AST::BuiltInSemantic::Variable::SVGroupIndex:
        return "[[thread_index_in_threadgroup]]"_str;
    default:
        ASSERT(builtInSemantic.variable() == AST::BuiltInSemantic::Variable::SVGroupThreadID);
        return "[[thread_position_in_threadgroup]]"_str;
    }
}

static String attributeForSemantic(AST::Semantic& semantic)
{
    if (WTF::holds_alternative<AST::BuiltInSemantic>(semantic))
        return attributeForSemantic(WTF::get<AST::BuiltInSemantic>(semantic));
    auto& stageInOutSemantic = WTF::get<AST::StageInOutSemantic>(semantic);
    return makeString("[[user(user", stageInOutSemantic.index(), ")]]");
}

EntryPointScaffolding::EntryPointScaffolding(AST::FunctionDefinition& functionDefinition, Intrinsics& intrinsics, TypeNamer& typeNamer, EntryPointItems& entryPointItems, HashMap<Binding*, size_t>& resourceMap, Layout& layout, std::function<MangledVariableName()>&& generateNextVariableName)
    : m_functionDefinition(functionDefinition)
    , m_intrinsics(intrinsics)
    , m_typeNamer(typeNamer)
    , m_entryPointItems(entryPointItems)
    , m_resourceMap(resourceMap)
    , m_layout(layout)
    , m_generateNextVariableName(generateNextVariableName)
{
    m_namedBindGroups.reserveInitialCapacity(m_layout.size());
    for (size_t i = 0; i < m_layout.size(); ++i) {
        NamedBindGroup namedBindGroup;
        namedBindGroup.structName = m_typeNamer.generateNextTypeName();
        namedBindGroup.variableName = m_generateNextVariableName();
        namedBindGroup.argumentBufferIndex = m_layout[i].name; // convertLayout() in GPURenderPipelineMetal.mm makes sure these don't collide.
        namedBindGroup.namedBindings.reserveInitialCapacity(m_layout[i].bindings.size());
        for (size_t j = 0; j < m_layout[i].bindings.size(); ++j) {
            NamedBinding namedBinding;
            namedBinding.elementName = m_typeNamer.generateNextStructureElementName();
            namedBinding.index = m_layout[i].bindings[j].internalName;
            WTF::visit(WTF::makeVisitor([&](UniformBufferBinding& uniformBufferBinding) {
                LengthInformation lengthInformation { m_typeNamer.generateNextStructureElementName(), m_generateNextVariableName(), uniformBufferBinding.lengthName };
                namedBinding.lengthInformation = lengthInformation;
            }, [&](SamplerBinding&) {
            }, [&](TextureBinding&) {
            }, [&](StorageBufferBinding& storageBufferBinding) {
                LengthInformation lengthInformation { m_typeNamer.generateNextStructureElementName(), m_generateNextVariableName(), storageBufferBinding.lengthName };
                namedBinding.lengthInformation = lengthInformation;
            }), m_layout[i].bindings[j].binding);
            namedBindGroup.namedBindings.uncheckedAppend(WTFMove(namedBinding));
        }
        m_namedBindGroups.uncheckedAppend(WTFMove(namedBindGroup));
    }

    for (size_t i = 0; i < m_entryPointItems.inputs.size(); ++i) {
        if (!WTF::holds_alternative<AST::BuiltInSemantic>(*m_entryPointItems.inputs[i].semantic))
            continue;
        NamedBuiltIn namedBuiltIn;
        namedBuiltIn.indexInEntryPointItems = i;
        namedBuiltIn.variableName = m_generateNextVariableName();
        m_namedBuiltIns.append(WTFMove(namedBuiltIn));
    }

    m_parameterVariables.reserveInitialCapacity(m_functionDefinition.parameters().size());
    for (size_t i = 0; i < m_functionDefinition.parameters().size(); ++i)
        m_parameterVariables.uncheckedAppend(m_generateNextVariableName());
}

void EntryPointScaffolding::emitResourceHelperTypes(StringBuilder& stringBuilder, Indentation<4> indent, ShaderStage shaderStage)
{
    for (size_t i = 0; i < m_layout.size(); ++i) {
        stringBuilder.append(indent, "struct ", m_namedBindGroups[i].structName, " {\n");
        {
            IndentationScope scope(indent);
            Vector<std::pair<unsigned, String>> structItems;
            for (size_t j = 0; j < m_layout[i].bindings.size(); ++j) {
                auto& binding = m_layout[i].bindings[j];
                if (!binding.visibility.contains(shaderStage))
                    continue;

                auto elementName = m_namedBindGroups[i].namedBindings[j].elementName;
                auto index = m_namedBindGroups[i].namedBindings[j].index;
                if (auto lengthInformation = m_namedBindGroups[i].namedBindings[j].lengthInformation)
                    structItems.append(std::make_pair(lengthInformation->index, makeString("uint2 ", lengthInformation->elementName, " [[id(", lengthInformation->index, ")]];")));

                auto iterator = m_resourceMap.find(&m_layout[i].bindings[j]);
                if (iterator != m_resourceMap.end()) {
                    auto& type = m_entryPointItems.inputs[iterator->value].unnamedType->unifyNode();
                    if (is<AST::UnnamedType>(type) && is<AST::ReferenceType>(downcast<AST::UnnamedType>(type))) {
                        auto& referenceType = downcast<AST::ReferenceType>(downcast<AST::UnnamedType>(type));
                        auto mangledTypeName = m_typeNamer.mangledNameForType(referenceType.elementType());
                        auto addressSpace = toString(referenceType.addressSpace());
                        structItems.append(std::make_pair(index, makeString(addressSpace, " ", mangledTypeName, "* ", elementName, " [[id(", index, ")]];")));
                    } else if (is<AST::NamedType>(type) && is<AST::NativeTypeDeclaration>(downcast<AST::NamedType>(type))) {
                        auto& namedType = downcast<AST::NativeTypeDeclaration>(downcast<AST::NamedType>(type));
                        auto mangledTypeName = m_typeNamer.mangledNameForType(namedType);
                        structItems.append(std::make_pair(index, makeString(mangledTypeName, ' ', elementName, " [[id(", index, ")]];")));
                    }
                } else {
                    // The binding doesn't appear in the shader source.
                    // However, we must still emit a placeholder, so successive items in the argument buffer struct have the correct offset.
                    // Because the binding doesn't appear in the shader source, we don't know which exact type the bind point should have.
                    // Therefore, we must synthesize a type out of thin air.
                    WTF::visit(WTF::makeVisitor([&](UniformBufferBinding) {
                        structItems.append(std::make_pair(index, makeString("constant void* ", elementName, " [[id(", index, ")]];")));
                    }, [&](SamplerBinding) {
                        structItems.append(std::make_pair(index, makeString("sampler ", elementName, " [[id(", index, ")]];")));
                    }, [&](TextureBinding) {
                        // FIXME: https://bugs.webkit.org/show_bug.cgi?id=201384 We don't know which texture type the binding represents. This is no good very bad.
                        structItems.append(std::make_pair(index, makeString("texture2d<float> ", elementName, " [[id(", index, ")]];")));
                    }, [&](StorageBufferBinding) {
                        structItems.append(std::make_pair(index, makeString("device void* ", elementName, " [[id(", index, ")]];")));
                    }), binding.binding);
                }
            }
            std::sort(structItems.begin(), structItems.end(), [](const std::pair<unsigned, String>& left, const std::pair<unsigned, String>& right) {
                return left.first < right.first;
            });
            for (const auto& structItem : structItems)
                stringBuilder.append(indent, structItem.second, '\n');
        }
        stringBuilder.append(indent, "};\n\n");
    }
}

bool EntryPointScaffolding::emitResourceSignature(StringBuilder& stringBuilder, IncludePrecedingComma includePrecedingComma)
{
    if (!m_layout.size())
        return false;

    if (includePrecedingComma == IncludePrecedingComma::Yes)
        stringBuilder.append(", ");

    for (size_t i = 0; i < m_layout.size(); ++i) {
        if (i)
            stringBuilder.append(", ");
        auto& namedBindGroup = m_namedBindGroups[i];
        stringBuilder.append("device ", namedBindGroup.structName, "& ", namedBindGroup.variableName, " [[buffer(", namedBindGroup.argumentBufferIndex, ")]]");
    }
    return true;
}

static StringView internalTypeForSemantic(const AST::BuiltInSemantic& builtInSemantic)
{
    switch (builtInSemantic.variable()) {
    case AST::BuiltInSemantic::Variable::SVInstanceID:
        return "uint";
    case AST::BuiltInSemantic::Variable::SVVertexID:
        return "uint";
    case AST::BuiltInSemantic::Variable::PSize:
        return "float";
    case AST::BuiltInSemantic::Variable::SVPosition:
        return "float4";
    case AST::BuiltInSemantic::Variable::SVIsFrontFace:
        return "bool";
    case AST::BuiltInSemantic::Variable::SVSampleIndex:
        return "uint";
    case AST::BuiltInSemantic::Variable::SVInnerCoverage:
        return "uint";
    case AST::BuiltInSemantic::Variable::SVTarget:
        return { };
    case AST::BuiltInSemantic::Variable::SVDepth:
        return "float";
    case AST::BuiltInSemantic::Variable::SVCoverage:
        return "uint";
    case AST::BuiltInSemantic::Variable::SVDispatchThreadID:
        return "uint3";
    case AST::BuiltInSemantic::Variable::SVGroupID:
        return "uint3";
    case AST::BuiltInSemantic::Variable::SVGroupIndex:
        return "uint";
    default:
        ASSERT(builtInSemantic.variable() == AST::BuiltInSemantic::Variable::SVGroupThreadID);
        return "uint3";
    }
}

bool EntryPointScaffolding::emitBuiltInsSignature(StringBuilder& stringBuilder, IncludePrecedingComma includePrecedingComma)
{
    if (!m_namedBuiltIns.size())
        return false;

    if (includePrecedingComma == IncludePrecedingComma::Yes)
        stringBuilder.append(", ");

    for (size_t i = 0; i < m_namedBuiltIns.size(); ++i) {
        if (i)
            stringBuilder.append(", ");
        auto& namedBuiltIn = m_namedBuiltIns[i];
        auto& item = m_entryPointItems.inputs[namedBuiltIn.indexInEntryPointItems];
        auto& builtInSemantic = WTF::get<AST::BuiltInSemantic>(*item.semantic);
        auto internalType = internalTypeForSemantic(builtInSemantic);
        if (!internalType.isNull())
            stringBuilder.append(internalType);
        else
            stringBuilder.append(m_typeNamer.mangledNameForType(*item.unnamedType));
        stringBuilder.append(' ', namedBuiltIn.variableName, ' ', attributeForSemantic(builtInSemantic));
    }
    return true;
}

void EntryPointScaffolding::emitMangledInputPath(StringBuilder& stringBuilder, Vector<String>& path)
{
    ASSERT(!path.isEmpty());
    bool found = false;
    AST::StructureDefinition* structureDefinition = nullptr;
    for (size_t i = 0; i < m_functionDefinition.parameters().size(); ++i) {
        if (m_functionDefinition.parameters()[i]->name() == path[0]) {
            stringBuilder.append(m_parameterVariables[i]);
            auto& unifyNode = m_functionDefinition.parameters()[i]->type()->unifyNode();
            if (is<AST::NamedType>(unifyNode)) {
                auto& namedType = downcast<AST::NamedType>(unifyNode);
                if (is<AST::StructureDefinition>(namedType))
                    structureDefinition = &downcast<AST::StructureDefinition>(namedType);
            }
            found = true;
            break;
        }
    }
    ASSERT(found);
    for (size_t i = 1; i < path.size(); ++i) {
        ASSERT(structureDefinition);
        auto* next = structureDefinition->find(path[i]);
        ASSERT(next);
        stringBuilder.append('.', m_typeNamer.mangledNameForStructureElement(*next));
        structureDefinition = nullptr;
        auto& unifyNode = next->type().unifyNode();
        if (is<AST::NamedType>(unifyNode)) {
            auto& namedType = downcast<AST::NamedType>(unifyNode);
            if (is<AST::StructureDefinition>(namedType))
                structureDefinition = &downcast<AST::StructureDefinition>(namedType);
        }
    }
}

void EntryPointScaffolding::emitMangledOutputPath(StringBuilder& stringBuilder, Vector<String>& path)
{
    AST::StructureDefinition* structureDefinition = nullptr;
    auto& unifyNode = m_functionDefinition.type().unifyNode();
    structureDefinition = &downcast<AST::StructureDefinition>(downcast<AST::NamedType>(unifyNode));
    for (auto& component : path) {
        ASSERT(structureDefinition);
        auto* next = structureDefinition->find(component);
        ASSERT(next);
        stringBuilder.append('.', m_typeNamer.mangledNameForStructureElement(*next));
        structureDefinition = nullptr;
        auto& unifyNode = next->type().unifyNode();
        if (is<AST::NamedType>(unifyNode)) {
            auto& namedType = downcast<AST::NamedType>(unifyNode);
            if (is<AST::StructureDefinition>(namedType))
                structureDefinition = &downcast<AST::StructureDefinition>(namedType);
        }
    }
}

void EntryPointScaffolding::emitUnpackResourcesAndNamedBuiltIns(StringBuilder& stringBuilder, Indentation<4> indent)
{
    for (size_t i = 0; i < m_functionDefinition.parameters().size(); ++i)
        stringBuilder.append(indent, m_typeNamer.mangledNameForType(*m_functionDefinition.parameters()[i]->type()), ' ', m_parameterVariables[i], ";\n");

    for (size_t i = 0; i < m_layout.size(); ++i) {
        auto variableName = m_namedBindGroups[i].variableName;
        for (size_t j = 0; j < m_layout[i].bindings.size(); ++j) {
            auto iterator = m_resourceMap.find(&m_layout[i].bindings[j]);
            if (iterator == m_resourceMap.end())
                continue;
            if (m_namedBindGroups[i].namedBindings[j].lengthInformation) {
                auto& path = m_entryPointItems.inputs[iterator->value].path;
                auto elementName = m_namedBindGroups[i].namedBindings[j].elementName;
                auto lengthElementName = m_namedBindGroups[i].namedBindings[j].lengthInformation->elementName;
                auto lengthTemporaryName = m_namedBindGroups[i].namedBindings[j].lengthInformation->temporaryName;

                auto& unnamedType = *m_entryPointItems.inputs[iterator->value].unnamedType;
                auto mangledTypeName = m_typeNamer.mangledNameForType(downcast<AST::ReferenceType>(unnamedType).elementType());

                stringBuilder.append(
                    indent, "size_t ", lengthTemporaryName, " = ", variableName, '.', lengthElementName, ".y;\n",
                    indent, lengthTemporaryName, " = ", lengthTemporaryName, " << 32;\n",
                    indent, lengthTemporaryName, " = ", lengthTemporaryName, " | ", variableName, '.', lengthElementName, ".x;\n",
                    indent, lengthTemporaryName, " = ", lengthTemporaryName, " / sizeof(", mangledTypeName, ");\n",
                    indent, "if (", lengthTemporaryName, " > 0xFFFFFFFF)\n",
                    indent, "    ", lengthTemporaryName, " = 0xFFFFFFFF;\n"
                );

                stringBuilder.append(indent);
                emitMangledInputPath(stringBuilder, path);
                stringBuilder.append(
                    " = { ", variableName, '.', elementName, ", static_cast<uint32_t>(", lengthTemporaryName, ") };\n"
                );
            } else {
                auto& path = m_entryPointItems.inputs[iterator->value].path;
                auto elementName = m_namedBindGroups[i].namedBindings[j].elementName;
                
                stringBuilder.append(indent);
                emitMangledInputPath(stringBuilder, path);
                stringBuilder.append(" = ", variableName, '.', elementName, ";\n");
            }
        }
    }

    for (auto& namedBuiltIn : m_namedBuiltIns) {
        auto& item = m_entryPointItems.inputs[namedBuiltIn.indexInEntryPointItems];
        auto& path = item.path;
        auto& variableName = namedBuiltIn.variableName;
        auto mangledTypeName = m_typeNamer.mangledNameForType(*item.unnamedType);

        stringBuilder.append(indent);
        emitMangledInputPath(stringBuilder, path);
        stringBuilder.append(" = ", mangledTypeName, '(', variableName, ");\n");
    }
}

VertexEntryPointScaffolding::VertexEntryPointScaffolding(AST::FunctionDefinition& functionDefinition, Intrinsics& intrinsics, TypeNamer& typeNamer, EntryPointItems& entryPointItems, HashMap<Binding*, size_t>& resourceMap, Layout& layout, std::function<MangledVariableName()>&& generateNextVariableName, HashMap<VertexAttribute*, size_t>& matchedVertexAttributes)
    : EntryPointScaffolding(functionDefinition, intrinsics, typeNamer, entryPointItems, resourceMap, layout, WTFMove(generateNextVariableName))
    , m_matchedVertexAttributes(matchedVertexAttributes)
    , m_stageInStructName(typeNamer.generateNextTypeName())
    , m_returnStructName(typeNamer.generateNextTypeName())
    , m_stageInParameterName(m_generateNextVariableName())
{
    m_namedStageIns.reserveInitialCapacity(m_matchedVertexAttributes.size());
    for (auto& keyValuePair : m_matchedVertexAttributes) {
        NamedStageIn namedStageIn;
        namedStageIn.indexInEntryPointItems = keyValuePair.value;
        namedStageIn.elementName = m_typeNamer.generateNextStructureElementName();
        namedStageIn.attributeIndex = keyValuePair.key->metalLocation;
        m_namedStageIns.uncheckedAppend(WTFMove(namedStageIn));
    }

    m_namedOutputs.reserveInitialCapacity(m_entryPointItems.outputs.size());
    for (size_t i = 0; i < m_entryPointItems.outputs.size(); ++i) {
        auto& outputItem = m_entryPointItems.outputs[i];
        NamedOutput namedOutput;
        namedOutput.elementName = m_typeNamer.generateNextStructureElementName();
        StringView internalType;
        if (WTF::holds_alternative<AST::BuiltInSemantic>(*outputItem.semantic))
            internalType = internalTypeForSemantic(WTF::get<AST::BuiltInSemantic>(*outputItem.semantic));
        if (!internalType.isNull())
            namedOutput.internalTypeName = internalType.toString();
        else
            namedOutput.internalTypeName = m_typeNamer.mangledNameForType(*outputItem.unnamedType);
        m_namedOutputs.uncheckedAppend(WTFMove(namedOutput));
    }
}

void VertexEntryPointScaffolding::emitHelperTypes(StringBuilder& stringBuilder, Indentation<4> indent)
{
    stringBuilder.append(indent, "struct ", m_stageInStructName, " {\n");
    {
        IndentationScope scope(indent);
        for (auto& namedStageIn : m_namedStageIns) {
            auto mangledTypeName = m_typeNamer.mangledNameForType(*m_entryPointItems.inputs[namedStageIn.indexInEntryPointItems].unnamedType);
            auto elementName = namedStageIn.elementName;
            auto attributeIndex = namedStageIn.attributeIndex;
            stringBuilder.append(indent, mangledTypeName, ' ', elementName, " [[attribute(", attributeIndex, ")]];\n");
        }
    }
    stringBuilder.append(
        indent, "};\n\n",
        indent, "struct ", m_returnStructName, " {\n"
    );
    {
        IndentationScope scope(indent);
        for (size_t i = 0; i < m_entryPointItems.outputs.size(); ++i) {
            auto& outputItem = m_entryPointItems.outputs[i];
            auto& internalTypeName = m_namedOutputs[i].internalTypeName;
            auto elementName = m_namedOutputs[i].elementName;
            auto attribute = attributeForSemantic(*outputItem.semantic);
            stringBuilder.append(indent, internalTypeName, ' ', elementName, ' ', attribute, ";\n");
        }
    }
    stringBuilder.append(indent, "};\n\n");
    
    emitResourceHelperTypes(stringBuilder, indent, ShaderStage::Vertex);
}

void VertexEntryPointScaffolding::emitSignature(StringBuilder& stringBuilder, MangledFunctionName functionName, Indentation<4> indent)
{
    stringBuilder.append(indent, "vertex ", m_returnStructName, ' ', functionName, '(', m_stageInStructName, ' ', m_stageInParameterName, " [[stage_in]]");
    emitResourceSignature(stringBuilder, IncludePrecedingComma::Yes);
    emitBuiltInsSignature(stringBuilder, IncludePrecedingComma::Yes);
    stringBuilder.append(")\n");
}

void VertexEntryPointScaffolding::emitUnpack(StringBuilder& stringBuilder, Indentation<4> indent)
{
    emitUnpackResourcesAndNamedBuiltIns(stringBuilder, indent);

    for (auto& namedStageIn : m_namedStageIns) {
        auto& path = m_entryPointItems.inputs[namedStageIn.indexInEntryPointItems].path;
        auto& elementName = namedStageIn.elementName;
        
        stringBuilder.append(indent);
        emitMangledInputPath(stringBuilder, path);
        stringBuilder.append(" = ", m_stageInParameterName, '.', elementName, ";\n");
    }
}

void VertexEntryPointScaffolding::emitPack(StringBuilder& stringBuilder, MangledVariableName inputVariableName, MangledVariableName outputVariableName, Indentation<4> indent)
{
    stringBuilder.append(indent, m_returnStructName, ' ', outputVariableName, ";\n");
    if (m_entryPointItems.outputs.size() == 1 && !m_entryPointItems.outputs[0].path.size()) {
        auto& elementName = m_namedOutputs[0].elementName;
        stringBuilder.append(indent, outputVariableName, '.', elementName, " = ", inputVariableName, ";\n");
        return;
    }
    for (size_t i = 0; i < m_entryPointItems.outputs.size(); ++i) {
        auto& elementName = m_namedOutputs[i].elementName;
        auto& internalTypeName = m_namedOutputs[i].internalTypeName;
        auto& path = m_entryPointItems.outputs[i].path;
        stringBuilder.append(indent, outputVariableName, '.', elementName, " = ", internalTypeName, '(', inputVariableName);
        emitMangledOutputPath(stringBuilder, path);
        stringBuilder.append(");\n");
    }
}

FragmentEntryPointScaffolding::FragmentEntryPointScaffolding(AST::FunctionDefinition& functionDefinition, Intrinsics& intrinsics, TypeNamer& typeNamer, EntryPointItems& entryPointItems, HashMap<Binding*, size_t>& resourceMap, Layout& layout, std::function<MangledVariableName()>&& generateNextVariableName, HashMap<AttachmentDescriptor*, size_t>&)
    : EntryPointScaffolding(functionDefinition, intrinsics, typeNamer, entryPointItems, resourceMap, layout, WTFMove(generateNextVariableName))
    , m_stageInStructName(typeNamer.generateNextTypeName())
    , m_returnStructName(typeNamer.generateNextTypeName())
    , m_stageInParameterName(m_generateNextVariableName())
{
    for (size_t i = 0; i < m_entryPointItems.inputs.size(); ++i) {
        auto& inputItem = m_entryPointItems.inputs[i];
        if (!WTF::holds_alternative<AST::StageInOutSemantic>(*inputItem.semantic))
            continue;
        auto& stageInOutSemantic = WTF::get<AST::StageInOutSemantic>(*inputItem.semantic);
        NamedStageIn namedStageIn;
        namedStageIn.indexInEntryPointItems = i;
        namedStageIn.elementName = m_typeNamer.generateNextStructureElementName();
        namedStageIn.attributeIndex = stageInOutSemantic.index();
        m_namedStageIns.append(WTFMove(namedStageIn));
    }

    m_namedOutputs.reserveInitialCapacity(m_entryPointItems.outputs.size());
    for (size_t i = 0; i < m_entryPointItems.outputs.size(); ++i) {
        auto& outputItem = m_entryPointItems.outputs[i];
        NamedOutput namedOutput;
        namedOutput.elementName = m_typeNamer.generateNextStructureElementName();
        StringView internalType;
        if (WTF::holds_alternative<AST::BuiltInSemantic>(*outputItem.semantic))
            internalType = internalTypeForSemantic(WTF::get<AST::BuiltInSemantic>(*outputItem.semantic));
        if (!internalType.isNull())
            namedOutput.internalTypeName = internalType.toString();
        else
            namedOutput.internalTypeName = m_typeNamer.mangledNameForType(*outputItem.unnamedType);
        m_namedOutputs.uncheckedAppend(WTFMove(namedOutput));
    }
}

void FragmentEntryPointScaffolding::emitHelperTypes(StringBuilder& stringBuilder, Indentation<4> indent)
{
    stringBuilder.append(indent, "struct ", m_stageInStructName, " {\n");
    {
        IndentationScope scope(indent);
        for (auto& namedStageIn : m_namedStageIns) {
            auto mangledTypeName = m_typeNamer.mangledNameForType(*m_entryPointItems.inputs[namedStageIn.indexInEntryPointItems].unnamedType);
            auto elementName = namedStageIn.elementName;
            auto attributeIndex = namedStageIn.attributeIndex;
            stringBuilder.append(indent, mangledTypeName, ' ', elementName, " [[user(user", attributeIndex, ")]];\n");
        }
    }
    stringBuilder.append(
        indent, "};\n\n",
        indent, "struct ", m_returnStructName, " {\n"
    );
    {
        IndentationScope scope(indent);
        for (size_t i = 0; i < m_entryPointItems.outputs.size(); ++i) {
            auto& outputItem = m_entryPointItems.outputs[i];
            auto& internalTypeName = m_namedOutputs[i].internalTypeName;
            auto elementName = m_namedOutputs[i].elementName;
            auto attribute = attributeForSemantic(*outputItem.semantic);
            stringBuilder.append(indent, internalTypeName, ' ', elementName, ' ', attribute, ";\n");
        }
    }
    stringBuilder.append(indent, "};\n\n");

    emitResourceHelperTypes(stringBuilder, indent, ShaderStage::Fragment);
}

void FragmentEntryPointScaffolding::emitSignature(StringBuilder& stringBuilder, MangledFunctionName functionName, Indentation<4> indent)
{
    stringBuilder.append(indent, "fragment ", m_returnStructName, ' ', functionName, '(', m_stageInStructName, ' ', m_stageInParameterName, " [[stage_in]]");
    emitResourceSignature(stringBuilder, IncludePrecedingComma::Yes);
    emitBuiltInsSignature(stringBuilder, IncludePrecedingComma::Yes);
    stringBuilder.append(")\n");
}

void FragmentEntryPointScaffolding::emitUnpack(StringBuilder& stringBuilder, Indentation<4> indent)
{
    emitUnpackResourcesAndNamedBuiltIns(stringBuilder, indent);

    for (auto& namedStageIn : m_namedStageIns) {
        auto& path = m_entryPointItems.inputs[namedStageIn.indexInEntryPointItems].path;
        auto& elementName = namedStageIn.elementName;

        stringBuilder.append(indent);
        emitMangledInputPath(stringBuilder, path);
        stringBuilder.append(" = ", m_stageInParameterName, '.', elementName, ";\n");
    }
}

void FragmentEntryPointScaffolding::emitPack(StringBuilder& stringBuilder, MangledVariableName inputVariableName, MangledVariableName outputVariableName, Indentation<4> indent)
{
    stringBuilder.append(indent, m_returnStructName, ' ', outputVariableName, ";\n");
    if (m_entryPointItems.outputs.size() == 1 && !m_entryPointItems.outputs[0].path.size()) {
        auto& elementName = m_namedOutputs[0].elementName;
        stringBuilder.append(indent, outputVariableName, '.', elementName, " = ", inputVariableName, ";\n");
        return;
    }
    for (size_t i = 0; i < m_entryPointItems.outputs.size(); ++i) {
        auto& elementName = m_namedOutputs[i].elementName;
        auto& internalTypeName = m_namedOutputs[i].internalTypeName;
        auto& path = m_entryPointItems.outputs[i].path;
        stringBuilder.append(indent, outputVariableName, '.', elementName, " = ", internalTypeName, '(', inputVariableName);
        emitMangledOutputPath(stringBuilder, path);
        stringBuilder.append(");\n");
    }
}

ComputeEntryPointScaffolding::ComputeEntryPointScaffolding(AST::FunctionDefinition& functionDefinition, Intrinsics& intrinsics, TypeNamer& typeNamer, EntryPointItems& entryPointItems, HashMap<Binding*, size_t>& resourceMap, Layout& layout, std::function<MangledVariableName()>&& generateNextVariableName)
    : EntryPointScaffolding(functionDefinition, intrinsics, typeNamer, entryPointItems, resourceMap, layout, WTFMove(generateNextVariableName))
{
}

void ComputeEntryPointScaffolding::emitHelperTypes(StringBuilder& stringBuilder, Indentation<4> indent)
{
    emitResourceHelperTypes(stringBuilder, indent, ShaderStage::Compute);
}

void ComputeEntryPointScaffolding::emitSignature(StringBuilder& stringBuilder, MangledFunctionName functionName, Indentation<4> indent)
{
    stringBuilder.append(indent, "kernel void ", functionName, '(');
    bool addedToSignature = emitResourceSignature(stringBuilder, IncludePrecedingComma::No);
    emitBuiltInsSignature(stringBuilder, addedToSignature ? IncludePrecedingComma::Yes : IncludePrecedingComma::No);
    stringBuilder.append(")\n");
}

void ComputeEntryPointScaffolding::emitUnpack(StringBuilder& stringBuilder, Indentation<4> indent)
{
    emitUnpackResourcesAndNamedBuiltIns(stringBuilder, indent);
}

void ComputeEntryPointScaffolding::emitPack(StringBuilder&, MangledVariableName, MangledVariableName, Indentation<4>)
{
    ASSERT_NOT_REACHED();
}

}

}

}

#endif
