void Debugger::FillDebugSourceVars()

in renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp [1566:2224]


void Debugger::FillDebugSourceVars(rdcarray<InstructionSourceInfo> &instInfo)
{
  for(InstructionSourceInfo &i : instInfo)
  {
    size_t offs = instructionOffsets[i.instruction];

    const ScopeData *scope = GetScope(offs);

    if(!scope)
      continue;

    // track which mappings we've processed, so if the same variable has mappings in multiple scopes
    // we only pick the innermost.
    rdcarray<LocalMapping> processed;
    rdcarray<Id> sourceVars;

    // capture the scopes upwards (from child to parent)
    rdcarray<const ScopeData *> scopes;
    while(scope)
    {
      scopes.push_back(scope);
      // if we reach a function scope, don't go up any further.
      if(scope->type == DebugScope::Function)
        break;

      scope = scope->parent;
    }

    // Iterate over the scopes downwards (parent->child)
    for(size_t s = 0; s < scopes.size(); ++s)
    {
      scope = scopes[scopes.size() - 1 - s];
      for(size_t m = 0; m < scope->localMappings.size(); m++)
      {
        const LocalMapping &mapping = scope->localMappings[m];

        // if this mapping is past the current instruction, stop here.
        if(mapping.instIndex > i.instruction)
          break;

        // see if this mapping is superceded by a later mapping in this scope for this instruction.
        // This is a bit inefficient but simple. The alternative would be to do record
        // start and end points for each mapping and update the end points, but this is simple and
        // should be limited since it's only per-scope
        bool supercede = false;
        for(size_t n = m + 1; n < scope->localMappings.size(); n++)
        {
          const LocalMapping &laterMapping = scope->localMappings[n];

          // if this mapping is past the current instruction, stop here.
          if(laterMapping.instIndex > i.instruction)
            break;

          // if this mapping will supercede and starts later
          if(laterMapping.isSourceSupersetOf(mapping) && laterMapping.instIndex > mapping.instIndex)
          {
            supercede = true;
            break;
          }
        }

        // don't add the current mapping if it's going to be superceded by something later
        if(supercede)
          continue;

        processed.push_back(mapping);
        Id sourceVar = mapping.sourceVar;
        if(!sourceVars.contains(mapping.sourceVar))
          sourceVars.push_back(mapping.sourceVar);
      }
    }

    // Converting debug variable mappings to SourceVariableMapping is a two phase algorithm.

    // Phase One
    // For each source variable, repeatedly apply the debug variable mappings.
    // This debug variable usage is tracked in a tree-like structure built using DebugVarNode
    // elements.
    // As each mapping is applied, the new mapping can fully or partially override the
    // existing mapping. When an existing mapping is:
    //  - fully overrideen: any sub-elements of that mapping are cleared
    //    i.e. assigning a vector, array, structure
    //  - partially overriden: the existing mapping is expanded into its sub-elements which are
    //    mapped to the current mapping and then the new mapping is set to its corresponding
    //    elements i.e. y-component in a vector, member in a structure, a single array element
    // The DebugVarNode member "emitSourceVar" determines if the DebugVar mapping should be
    // converted to a source variable mapping.

    // Phase Two
    // The DebugVarNode tree is walked to find the nodes which have "emitSourceVar" set to true and
    // then those nodes are converted to SourceVariableMapping

    struct DebugVarNode
    {
      rdcarray<DebugVarNode> children;
      Id debugVar;
      rdcstr name;
      rdcstr debugVarSuffix;
      VarType type = VarType::Unknown;
      uint32_t rows = 0;
      uint32_t columns = 0;
      uint32_t debugVarComponent = 0;
      uint32_t offset = 0;
      bool emitSourceVar = false;
    };

    ::std::map<Id, DebugVarNode> roots;

    // Phase One: generate the DebugVarNode tree by repeatedly apply debug variables updating
    // existing mappings with later mappings
    for(size_t sv = 0; sv < sourceVars.size(); ++sv)
    {
      Id sourceVarId = sourceVars[sv];
      const LocalData &l = m_DebugInfo.locals[sourceVarId];

      // Convert processed mappings into a usage map
      for(size_t m = 0; m < processed.size(); ++m)
      {
        const LocalMapping &mapping = processed[m];
        if(mapping.sourceVar != sourceVarId)
          continue;

        const TypeData *typeWalk = l.type;
        DebugVarNode *usage = &roots[sourceVarId];
        if(usage->name.isEmpty())
        {
          usage->name = l.name;
          usage->rows = 1U;
          usage->columns = 1U;
        }

        // if it doesn't have indexes this is simple, set up a 1:1 map
        if(mapping.indexes.isEmpty())
        {
          uint32_t rows = 1;
          uint32_t columns = 1;
          // skip past any pointer types to get the 'real' type that we'll see
          while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::GPUPointer)
            typeWalk = &m_DebugInfo.types[typeWalk->baseType];

          const uint32_t arrayDimension = typeWalk->arrayDimensions.size();
          if(arrayDimension > 0)
          {
            // walk down until we get to a scalar type, if we get there. This means arrays of
            // basic types will get the right type
            while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown)
              typeWalk = &m_DebugInfo.types[typeWalk->baseType];

            usage->type = typeWalk->type;
          }
          else if(!typeWalk->structMembers.empty())
          {
            usage->type = typeWalk->type;
          }
          if(typeWalk->matSize != 0)
          {
            const TypeData &vec = m_DebugInfo.types[typeWalk->baseType];
            const TypeData &scalar = m_DebugInfo.types[vec.baseType];

            usage->type = scalar.type;

            if(typeWalk->colMajorMat)
            {
              rows = RDCMAX(1U, vec.vecSize);
              columns = RDCMAX(1U, typeWalk->matSize);
            }
            else
            {
              columns = RDCMAX(1U, vec.vecSize);
              rows = RDCMAX(1U, typeWalk->matSize);
            }
          }
          else if(typeWalk->vecSize != 0)
          {
            const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType];

            usage->type = scalar.type;
            columns = RDCMAX(1U, typeWalk->vecSize);
          }

          usage->debugVar = mapping.debugVar;
          // Remove any child mappings : this mapping covers everything
          usage->children.clear();
          usage->emitSourceVar = true;
          usage->rows = rows;
          usage->columns = columns;
        }
        else
        {
          rdcarray<uint32_t> indexes = mapping.indexes;

          // walk any aggregate types
          while(!indexes.empty())
          {
            uint32_t idx = ~0U;
            const TypeData *childType = NULL;
            const uint32_t arrayDimension = typeWalk->arrayDimensions.size();
            if(arrayDimension > 0)
            {
              const rdcarray<uint32_t> &dims = typeWalk->arrayDimensions;
              uint32_t numIdxs = (uint32_t)indexes.size();
              childType = &m_DebugInfo.types[typeWalk->baseType];
              uint32_t childRows = 1U;
              uint32_t childColumns = 1U;
              VarType elementType = childType->type;
              uint32_t elementSize = 1;
              if(childType->matSize != 0)
              {
                const TypeData &vec = m_DebugInfo.types[childType->baseType];
                const TypeData &scalar = m_DebugInfo.types[vec.baseType];

                elementType = scalar.type;
                if(childType->colMajorMat)
                {
                  childRows = RDCMAX(1U, vec.vecSize);
                  childColumns = RDCMAX(1U, childType->matSize);
                }
                else
                {
                  childColumns = RDCMAX(1U, vec.vecSize);
                  childRows = RDCMAX(1U, childType->matSize);
                }
              }
              else if(childType->vecSize != 0)
              {
                const TypeData &scalar = m_DebugInfo.types[childType->baseType];
                uint32_t vecColumns = RDCMAX(1U, childType->vecSize);

                elementType = scalar.type;

                childRows = 1U;
                childColumns = vecColumns;
              }
              else if(!childType->structMembers.empty())
              {
                elementSize += childType->memberOffsets[childType->memberOffsets.count() - 1];
              }
              elementSize *= childRows * childColumns;
              const uint32_t countDims = RDCMIN(arrayDimension, numIdxs);
              // handle N dimensional arrays
              for(uint32_t d = 0; d < countDims; ++d)
              {
                idx = indexes[0];
                indexes.erase(0);
                uint32_t rows = dims[d];
                usage->rows = rows;
                usage->columns = 1U;
                // Expand the node if required
                if(usage->children.isEmpty())
                {
                  usage->children.resize(rows);
                  for(uint32_t x = 0; x < rows; x++)
                  {
                    usage->children[x].debugVar = usage->debugVar;
                    rdcstr suffix = StringFormat::Fmt("[%u]", x);
                    usage->children[x].debugVarSuffix = usage->debugVarSuffix + suffix;
                    usage->children[x].name = usage->name + suffix;
                    usage->children[x].type = elementType;
                    usage->children[x].rows = childRows;
                    usage->children[x].columns = childColumns;
                    usage->children[x].offset = usage->offset + x * elementSize;
                  }
                }
                RDCASSERTEQUAL(usage->children.size(), rows);
                // if the whole node was displayed : display the sub-elements
                if(usage->emitSourceVar)
                {
                  for(uint32_t x = 0; x < rows; x++)
                    usage->children[x].emitSourceVar = true;
                  usage->emitSourceVar = false;
                }
                usage = &usage->children[idx];
                usage->type = childType->type;
                typeWalk = childType;
              }
            }
            else if(!typeWalk->structMembers.empty())
            {
              idx = indexes[0];
              indexes.erase(0);
              childType = &m_DebugInfo.types[typeWalk->structMembers[idx].second];
              uint32_t rows = typeWalk->structMembers.size();
              usage->rows = rows;
              usage->columns = 1U;
              // Expand the node if required
              if(usage->children.isEmpty())
              {
                usage->children.resize(rows);
                for(uint32_t x = 0; x < rows; x++)
                {
                  rdcstr suffix = StringFormat::Fmt(".%s", typeWalk->structMembers[x].first.c_str());
                  usage->children[x].debugVar = usage->debugVar;
                  usage->children[x].debugVarSuffix = usage->debugVarSuffix + suffix;
                  usage->children[x].name = usage->name + suffix;
                  usage->children[x].offset = usage->offset + typeWalk->memberOffsets[x];
                  uint32_t memberRows = 1U;
                  uint32_t memberColumns = 1U;
                  const TypeData *memberType = &m_DebugInfo.types[typeWalk->structMembers[x].second];
                  VarType elementType = memberType->type;
                  if(memberType->matSize != 0)
                  {
                    const TypeData &vec = m_DebugInfo.types[memberType->baseType];
                    const TypeData &scalar = m_DebugInfo.types[vec.baseType];

                    elementType = scalar.type;
                    if(memberType->colMajorMat)
                    {
                      memberRows = RDCMAX(1U, vec.vecSize);
                      memberColumns = RDCMAX(1U, memberType->matSize);
                    }
                    else
                    {
                      memberColumns = RDCMAX(1U, vec.vecSize);
                      memberRows = RDCMAX(1U, memberType->matSize);
                    }
                  }
                  else if(memberType->vecSize != 0)
                  {
                    const TypeData &scalar = m_DebugInfo.types[memberType->baseType];
                    uint32_t vecColumns = RDCMAX(1U, memberType->vecSize);

                    elementType = scalar.type;

                    memberRows = 1U;
                    memberColumns = vecColumns;
                  }
                  usage->children[x].type = elementType;
                  usage->children[x].rows = memberRows;
                  usage->children[x].columns = memberColumns;
                }
              }
              RDCASSERTEQUAL(usage->children.size(), rows);
              // if the whole node was displayed : display the sub-elements
              if(usage->emitSourceVar)
              {
                for(uint32_t x = 0; x < rows; x++)
                  usage->children[x].emitSourceVar = true;
                usage->emitSourceVar = false;
              }

              usage = &usage->children[idx];
              usage->type = childType->type;
              typeWalk = childType;
            }
            else
            {
              break;
            }
          }

          const char swizzle[] = "xyzw";
          uint32_t rows = 1U;
          uint32_t columns = 1U;
          size_t countRemainingIndexes = indexes.size();
          if(typeWalk->matSize != 0)
          {
            const TypeData &vec = m_DebugInfo.types[typeWalk->baseType];
            const TypeData &scalar = m_DebugInfo.types[vec.baseType];

            usage->type = scalar.type;

            if(typeWalk->colMajorMat)
            {
              rows = RDCMAX(1U, vec.vecSize);
              columns = RDCMAX(1U, typeWalk->matSize);
            }
            else
            {
              columns = RDCMAX(1U, vec.vecSize);
              rows = RDCMAX(1U, typeWalk->matSize);
            }
            usage->rows = rows;
            usage->columns = columns;

            if((countRemainingIndexes == 2) || (countRemainingIndexes == 1))
            {
              if(usage->children.isEmpty())
              {
                // Matrices are stored as [row][col]
                usage->children.resize(rows);
                for(uint32_t r = 0; r < rows; ++r)
                {
                  usage->children[r].emitSourceVar = false;
                  usage->children[r].name = usage->name + StringFormat::Fmt(".row%u", r);
                  usage->children[r].type = scalar.type;
                  usage->children[r].debugVar = usage->debugVar;
                  usage->children[r].debugVarComponent = 0;
                  usage->children[r].rows = 1U;
                  usage->children[r].columns = columns;
                  usage->children[r].offset = usage->offset + r * rows;
                  usage->children[r].children.resize(columns);
                  for(uint32_t c = 0; c < columns; ++c)
                  {
                    usage->children[r].children[c].emitSourceVar = false;
                    usage->children[r].children[c].name =
                        usage->name + StringFormat::Fmt(".row%u.%c", r, swizzle[RDCMIN(c, 3U)]);
                    usage->children[r].children[c].type = scalar.type;
                    usage->children[r].children[c].debugVar = usage->debugVar;
                    usage->children[r].children[c].debugVarComponent = r;
                    usage->children[r].children[c].rows = 1U;
                    usage->children[r].children[c].columns = 1U;
                    usage->children[r].children[c].offset = usage->children[r].offset + c;
                  }
                }
              }
              RDCASSERTEQUAL(usage->children.size(), rows);

              // two remaining indices selects a scalar within the matrix
              if(countRemainingIndexes == 2)
              {
                uint32_t row, col;

                if(typeWalk->colMajorMat)
                {
                  col = indexes[0];
                  row = indexes[1];
                }
                else
                {
                  row = indexes[0];
                  col = indexes[1];
                }
                RDCASSERT(row < rows, row, rows);
                RDCASSERT(col < columns, col, columns);

                RDCASSERTEQUAL(usage->children[row].children.size(), columns);
                usage->children[row].children[col].emitSourceVar =
                    !usage->children[row].emitSourceVar;
                usage->children[row].children[col].debugVar = mapping.debugVar;
                usage->children[row].children[col].debugVarComponent = 0;

                // try to recombine matrix rows to a single source var display
                if(!usage->children[row].emitSourceVar)
                {
                  bool collapseVector = true;
                  for(uint32_t c = 0; c < columns; ++c)
                  {
                    collapseVector = usage->children[row].children[c].emitSourceVar;
                    if(!collapseVector)
                      break;
                  }
                  if(collapseVector)
                  {
                    usage->children[row].emitSourceVar = true;
                    for(uint32_t c = 0; c < columns; ++c)
                      usage->children[row].children[c].emitSourceVar = false;
                  }
                }
              }
              else
              {
                if(typeWalk->colMajorMat)
                {
                  uint32_t col = indexes[0];
                  RDCASSERT(col < columns, col, columns);
                  // one remaining index selects a column within the matrix.
                  // source vars are displayed as row-major, need <rows> mappings
                  for(uint32_t r = 0; r < rows; ++r)
                  {
                    RDCASSERTEQUAL(usage->children[r].children.size(), columns);
                    usage->children[r].children[col].emitSourceVar =
                        !usage->children[r].emitSourceVar;
                    usage->children[r].children[col].debugVar = mapping.debugVar;
                    usage->children[r].children[col].debugVarComponent = r;
                  }
                }
                else
                {
                  uint32_t row = indexes[0];
                  RDCASSERT(row < rows, row, rows);
                  RDCASSERTEQUAL(usage->children.size(), rows);
                  RDCASSERTEQUAL(usage->children[row].children.size(), columns);
                  // one remaining index selects a row within the matrix.
                  // source vars are displayed as row-major, need <rows> mappings
                  for(uint32_t c = 0; c < columns; ++c)
                  {
                    usage->children[row].children[c].emitSourceVar =
                        !usage->children[row].emitSourceVar;
                    usage->children[row].children[c].debugVar = mapping.debugVar;
                    usage->children[row].children[c].debugVarComponent = c;
                  }
                }
              }
              // try to recombine matrix rows to a single source var display
              for(uint32_t r = 0; r < rows; ++r)
              {
                if(!usage->children[r].emitSourceVar)
                {
                  bool collapseVector = true;
                  RDCASSERTEQUAL(usage->children[r].children.size(), columns);
                  for(uint32_t c = 0; c < columns; ++c)
                  {
                    collapseVector = usage->children[r].children[c].emitSourceVar;
                    if(!collapseVector)
                      break;
                  }
                  if(collapseVector)
                  {
                    usage->children[r].emitSourceVar = true;
                    for(uint32_t c = 0; c < columns; ++c)
                      usage->children[r].children[c].emitSourceVar = false;
                  }
                }
              }
              usage->emitSourceVar = false;
            }
            else
            {
              RDCASSERTEQUAL(countRemainingIndexes, 0);
              // Remove mappings : this mapping covers everything
              usage->debugVar = mapping.debugVar;
              usage->children.clear();
              usage->emitSourceVar = true;
              usage->debugVarSuffix.clear();
            }
          }
          else if(typeWalk->vecSize != 0)
          {
            const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType];
            columns = RDCMAX(1U, typeWalk->vecSize);

            usage->type = scalar.type;

            usage->rows = 1U;
            usage->columns = columns;

            // remaining index selects a scalar within the vector
            if(countRemainingIndexes == 1)
            {
              if(usage->children.isEmpty())
              {
                usage->children.resize(columns);
                for(uint32_t x = 0; x < columns; ++x)
                {
                  usage->children[x].emitSourceVar = usage->emitSourceVar;
                  usage->children[x].name =
                      usage->name + StringFormat::Fmt(".%c", swizzle[RDCMIN(x, 3U)]);
                  usage->children[x].type = scalar.type;
                  usage->children[x].debugVar = usage->debugVar;
                  usage->children[x].debugVarComponent = x;
                  usage->children[x].rows = 1U;
                  usage->children[x].columns = 1U;
                  usage->children[x].offset = usage->offset + x;
                }
                usage->emitSourceVar = false;
              }
              uint32_t col = indexes[0];
              RDCASSERT(col < columns, col, columns);
              RDCASSERTEQUAL(usage->children.size(), columns);
              usage->children[col].debugVar = mapping.debugVar;
              usage->children[col].debugVarComponent = 0;
              usage->children[col].emitSourceVar = true;

              // try to recombine vector to a single source var display
              bool collapseVector = true;
              for(uint32_t x = 0; x < columns; ++x)
              {
                collapseVector = usage->children[x].emitSourceVar;
                if(!collapseVector)
                  break;
              }
              if(collapseVector)
              {
                usage->emitSourceVar = true;
                for(uint32_t x = 0; x < columns; ++x)
                  usage->children[x].emitSourceVar = false;
              }
            }
            else
            {
              RDCASSERTEQUAL(countRemainingIndexes, 0);
              // Remove mappings : this mapping covers everything
              usage->debugVar = mapping.debugVar;
              usage->children.clear();
              usage->emitSourceVar = true;
              usage->debugVarSuffix.clear();
            }
          }
          else
          {
            // walk down until we get to a scalar type, if we get there. This means arrays of
            // basic types will get the right type
            while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown)
              typeWalk = &m_DebugInfo.types[typeWalk->baseType];

            usage->type = typeWalk->type;
            usage->debugVar = mapping.debugVar;
            usage->debugVarComponent = 0;
            usage->rows = 1U;
            usage->columns = 1U;
            usage->emitSourceVar = true;
            usage->children.clear();
            usage->debugVarSuffix.clear();
          }
        }
      }
    }

    // Phase Two: walk the DebugVarNode tree and convert "emitSourceVar = true" nodes to a SourceVariableMapping
    for(size_t sv = 0; sv < sourceVars.size(); ++sv)
    {
      Id sourceVarId = sourceVars[sv];
      DebugVarNode *usage = &roots[sourceVarId];
      rdcarray<const DebugVarNode *> nodesToProcess;
      rdcarray<const DebugVarNode *> sourceVarNodes;
      nodesToProcess.push_back(usage);
      while(!nodesToProcess.isEmpty())
      {
        const DebugVarNode *n = nodesToProcess.back();
        nodesToProcess.pop_back();
        if(n->emitSourceVar)
        {
          sourceVarNodes.push_back(n);
        }
        else
        {
          for(size_t x = 0; x < n->children.size(); ++x)
          {
            const DebugVarNode *child = &n->children[x];
            nodesToProcess.push_back(child);
          }
        }
      }
      for(size_t x = 0; x < sourceVarNodes.size(); ++x)
      {
        const DebugVarNode *n = sourceVarNodes[x];
        SourceVariableMapping sourceVar;
        sourceVar.name = n->name;
        sourceVar.type = n->type;
        sourceVar.signatureIndex = -1;
        sourceVar.offset = n->offset;
        sourceVar.variables.clear();
        // unknown is treated as a struct
        if(sourceVar.type == VarType::Unknown)
          sourceVar.type = VarType::Struct;

        if(n->children.empty())
        {
          RDCASSERTNOTEQUAL(n->rows * n->columns, 0);
          for(uint32_t c = 0; c < n->rows * n->columns; ++c)
          {
            sourceVar.variables.push_back(DebugVariableReference(
                DebugVariableType::Variable, GetRawName(n->debugVar) + n->debugVarSuffix, c));
          }
        }
        else
        {
          RDCASSERTEQUAL(n->rows * n->columns, (uint32_t)n->children.count());
          for(int32_t c = 0; c < n->children.count(); ++c)
            sourceVar.variables.push_back(DebugVariableReference(
                DebugVariableType::Variable,
                GetRawName(n->children[c].debugVar) + n->children[c].debugVarSuffix,
                n->children[c].debugVarComponent));
        }
        i.sourceVars.push_back(sourceVar);
      }
    }
  }
}