mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 14:19:46 -06:00
Externals: Update glslang.
This updates glslang to commit 4fc7a33910fb8e40b970d160e1b38ab3f67fe0f3 which is the current version listed in the known_good.json file for the version 1.2.131.2 of the Vulkan-ValidationLayers repo.
This commit is contained in:
553
Externals/glslang/SPIRV/SpvBuilder.cpp
vendored
553
Externals/glslang/SPIRV/SpvBuilder.cpp
vendored
@ -1,6 +1,6 @@
|
||||
//
|
||||
// Copyright (C) 2014-2015 LunarG, Inc.
|
||||
// Copyright (C) 2015-2016 Google, Inc.
|
||||
// Copyright (C) 2015-2018 Google, Inc.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
@ -46,7 +46,9 @@
|
||||
|
||||
#include "SpvBuilder.h"
|
||||
|
||||
#ifndef GLSLANG_WEB
|
||||
#include "hex_float.h"
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cstdio>
|
||||
@ -60,6 +62,7 @@ Builder::Builder(unsigned int spvVersion, unsigned int magicNumber, SpvBuildLogg
|
||||
sourceVersion(0),
|
||||
sourceFileStringId(NoResult),
|
||||
currentLine(0),
|
||||
currentFile(nullptr),
|
||||
emitOpLines(false),
|
||||
addressModel(AddressingModelLogical),
|
||||
memoryModel(MemoryModelGLSL450),
|
||||
@ -81,13 +84,15 @@ Id Builder::import(const char* name)
|
||||
{
|
||||
Instruction* import = new Instruction(getUniqueId(), NoType, OpExtInstImport);
|
||||
import->addStringOperand(name);
|
||||
module.mapInstruction(import);
|
||||
|
||||
imports.push_back(std::unique_ptr<Instruction>(import));
|
||||
return import->getResultId();
|
||||
}
|
||||
|
||||
// Emit an OpLine if we've been asked to emit OpLines and the line number
|
||||
// has changed since the last time, and is a valid line number.
|
||||
// Emit instruction for non-filename-based #line directives (ie. no filename
|
||||
// seen yet): emit an OpLine if we've been asked to emit OpLines and the line
|
||||
// number has changed since the last time, and is a valid line number.
|
||||
void Builder::setLine(int lineNum)
|
||||
{
|
||||
if (lineNum != 0 && lineNum != currentLine) {
|
||||
@ -97,6 +102,26 @@ void Builder::setLine(int lineNum)
|
||||
}
|
||||
}
|
||||
|
||||
// If no filename, do non-filename-based #line emit. Else do filename-based emit.
|
||||
// Emit OpLine if we've been asked to emit OpLines and the line number or filename
|
||||
// has changed since the last time, and line number is valid.
|
||||
void Builder::setLine(int lineNum, const char* filename)
|
||||
{
|
||||
if (filename == nullptr) {
|
||||
setLine(lineNum);
|
||||
return;
|
||||
}
|
||||
if ((lineNum != 0 && lineNum != currentLine) || currentFile == nullptr ||
|
||||
strncmp(filename, currentFile, strlen(currentFile) + 1) != 0) {
|
||||
currentLine = lineNum;
|
||||
currentFile = filename;
|
||||
if (emitOpLines) {
|
||||
spv::Id strId = getStringId(filename);
|
||||
addLine(strId, currentLine, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Builder::addLine(Id fileName, int lineNum, int column)
|
||||
{
|
||||
Instruction* line = new Instruction(OpLine);
|
||||
@ -171,8 +196,47 @@ Id Builder::makePointer(StorageClass storageClass, Id pointee)
|
||||
return type->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::makeForwardPointer(StorageClass storageClass)
|
||||
{
|
||||
// Caching/uniquifying doesn't work here, because we don't know the
|
||||
// pointee type and there can be multiple forward pointers of the same
|
||||
// storage type. Somebody higher up in the stack must keep track.
|
||||
Instruction* type = new Instruction(getUniqueId(), NoType, OpTypeForwardPointer);
|
||||
type->addImmediateOperand(storageClass);
|
||||
constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type));
|
||||
module.mapInstruction(type);
|
||||
|
||||
return type->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::makePointerFromForwardPointer(StorageClass storageClass, Id forwardPointerType, Id pointee)
|
||||
{
|
||||
// try to find it
|
||||
Instruction* type;
|
||||
for (int t = 0; t < (int)groupedTypes[OpTypePointer].size(); ++t) {
|
||||
type = groupedTypes[OpTypePointer][t];
|
||||
if (type->getImmediateOperand(0) == (unsigned)storageClass &&
|
||||
type->getIdOperand(1) == pointee)
|
||||
return type->getResultId();
|
||||
}
|
||||
|
||||
type = new Instruction(forwardPointerType, NoType, OpTypePointer);
|
||||
type->addImmediateOperand(storageClass);
|
||||
type->addIdOperand(pointee);
|
||||
groupedTypes[OpTypePointer].push_back(type);
|
||||
constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type));
|
||||
module.mapInstruction(type);
|
||||
|
||||
return type->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::makeIntegerType(int width, bool hasSign)
|
||||
{
|
||||
#ifdef GLSLANG_WEB
|
||||
assert(width == 32);
|
||||
width = 32;
|
||||
#endif
|
||||
|
||||
// try to find it
|
||||
Instruction* type;
|
||||
for (int t = 0; t < (int)groupedTypes[OpTypeInt].size(); ++t) {
|
||||
@ -193,10 +257,8 @@ Id Builder::makeIntegerType(int width, bool hasSign)
|
||||
// deal with capabilities
|
||||
switch (width) {
|
||||
case 8:
|
||||
addCapability(CapabilityInt8);
|
||||
break;
|
||||
case 16:
|
||||
addCapability(CapabilityInt16);
|
||||
// these are currently handled by storage-type declarations and post processing
|
||||
break;
|
||||
case 64:
|
||||
addCapability(CapabilityInt64);
|
||||
@ -210,6 +272,11 @@ Id Builder::makeIntegerType(int width, bool hasSign)
|
||||
|
||||
Id Builder::makeFloatType(int width)
|
||||
{
|
||||
#ifdef GLSLANG_WEB
|
||||
assert(width == 32);
|
||||
width = 32;
|
||||
#endif
|
||||
|
||||
// try to find it
|
||||
Instruction* type;
|
||||
for (int t = 0; t < (int)groupedTypes[OpTypeFloat].size(); ++t) {
|
||||
@ -228,7 +295,7 @@ Id Builder::makeFloatType(int width)
|
||||
// deal with capabilities
|
||||
switch (width) {
|
||||
case 16:
|
||||
addCapability(CapabilityFloat16);
|
||||
// currently handled by storage-type declarations and post processing
|
||||
break;
|
||||
case 64:
|
||||
addCapability(CapabilityFloat64);
|
||||
@ -333,6 +400,33 @@ Id Builder::makeMatrixType(Id component, int cols, int rows)
|
||||
return type->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::makeCooperativeMatrixType(Id component, Id scope, Id rows, Id cols)
|
||||
{
|
||||
// try to find it
|
||||
Instruction* type;
|
||||
for (int t = 0; t < (int)groupedTypes[OpTypeCooperativeMatrixNV].size(); ++t) {
|
||||
type = groupedTypes[OpTypeCooperativeMatrixNV][t];
|
||||
if (type->getIdOperand(0) == component &&
|
||||
type->getIdOperand(1) == scope &&
|
||||
type->getIdOperand(2) == rows &&
|
||||
type->getIdOperand(3) == cols)
|
||||
return type->getResultId();
|
||||
}
|
||||
|
||||
// not found, make it
|
||||
type = new Instruction(getUniqueId(), NoType, OpTypeCooperativeMatrixNV);
|
||||
type->addIdOperand(component);
|
||||
type->addIdOperand(scope);
|
||||
type->addIdOperand(rows);
|
||||
type->addIdOperand(cols);
|
||||
groupedTypes[OpTypeCooperativeMatrixNV].push_back(type);
|
||||
constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type));
|
||||
module.mapInstruction(type);
|
||||
|
||||
return type->getResultId();
|
||||
}
|
||||
|
||||
|
||||
// TODO: performance: track arrays per stride
|
||||
// If a stride is supplied (non-zero) make an array.
|
||||
// If no stride (0), reuse previous array types.
|
||||
@ -434,6 +528,7 @@ Id Builder::makeImageType(Id sampledType, Dim dim, bool depth, bool arrayed, boo
|
||||
constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type));
|
||||
module.mapInstruction(type);
|
||||
|
||||
#ifndef GLSLANG_WEB
|
||||
// deal with capabilities
|
||||
switch (dim) {
|
||||
case DimBuffer:
|
||||
@ -479,6 +574,7 @@ Id Builder::makeImageType(Id sampledType, Dim dim, bool depth, bool arrayed, boo
|
||||
addCapability(CapabilityImageMSArray);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return type->getResultId();
|
||||
}
|
||||
@ -504,12 +600,29 @@ Id Builder::makeSampledImageType(Id imageType)
|
||||
return type->getResultId();
|
||||
}
|
||||
|
||||
#ifndef GLSLANG_WEB
|
||||
Id Builder::makeAccelerationStructureNVType()
|
||||
{
|
||||
Instruction *type;
|
||||
if (groupedTypes[OpTypeAccelerationStructureNV].size() == 0) {
|
||||
type = new Instruction(getUniqueId(), NoType, OpTypeAccelerationStructureNV);
|
||||
groupedTypes[OpTypeAccelerationStructureNV].push_back(type);
|
||||
constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type));
|
||||
module.mapInstruction(type);
|
||||
} else {
|
||||
type = groupedTypes[OpTypeAccelerationStructureNV].back();
|
||||
}
|
||||
|
||||
return type->getResultId();
|
||||
}
|
||||
#endif
|
||||
|
||||
Id Builder::getDerefTypeId(Id resultId) const
|
||||
{
|
||||
Id typeId = getTypeId(resultId);
|
||||
assert(isPointerType(typeId));
|
||||
|
||||
return module.getInstruction(typeId)->getImmediateOperand(1);
|
||||
return module.getInstruction(typeId)->getIdOperand(1);
|
||||
}
|
||||
|
||||
Op Builder::getMostBasicTypeClass(Id typeId) const
|
||||
@ -519,12 +632,6 @@ Op Builder::getMostBasicTypeClass(Id typeId) const
|
||||
Op typeClass = instr->getOpCode();
|
||||
switch (typeClass)
|
||||
{
|
||||
case OpTypeVoid:
|
||||
case OpTypeBool:
|
||||
case OpTypeInt:
|
||||
case OpTypeFloat:
|
||||
case OpTypeStruct:
|
||||
return typeClass;
|
||||
case OpTypeVector:
|
||||
case OpTypeMatrix:
|
||||
case OpTypeArray:
|
||||
@ -533,8 +640,7 @@ Op Builder::getMostBasicTypeClass(Id typeId) const
|
||||
case OpTypePointer:
|
||||
return getMostBasicTypeClass(instr->getIdOperand(1));
|
||||
default:
|
||||
assert(0);
|
||||
return OpTypeFloat;
|
||||
return typeClass;
|
||||
}
|
||||
}
|
||||
|
||||
@ -547,17 +653,21 @@ int Builder::getNumTypeConstituents(Id typeId) const
|
||||
case OpTypeBool:
|
||||
case OpTypeInt:
|
||||
case OpTypeFloat:
|
||||
case OpTypePointer:
|
||||
return 1;
|
||||
case OpTypeVector:
|
||||
case OpTypeMatrix:
|
||||
return instr->getImmediateOperand(1);
|
||||
case OpTypeArray:
|
||||
{
|
||||
Id lengthId = instr->getImmediateOperand(1);
|
||||
Id lengthId = instr->getIdOperand(1);
|
||||
return module.getInstruction(lengthId)->getImmediateOperand(0);
|
||||
}
|
||||
case OpTypeStruct:
|
||||
return instr->getNumOperands();
|
||||
case OpTypeCooperativeMatrixNV:
|
||||
// has only one constituent when used with OpCompositeConstruct.
|
||||
return 1;
|
||||
default:
|
||||
assert(0);
|
||||
return 1;
|
||||
@ -604,6 +714,7 @@ Id Builder::getContainedTypeId(Id typeId, int member) const
|
||||
case OpTypeMatrix:
|
||||
case OpTypeArray:
|
||||
case OpTypeRuntimeArray:
|
||||
case OpTypeCooperativeMatrixNV:
|
||||
return instr->getIdOperand(0);
|
||||
case OpTypePointer:
|
||||
return instr->getIdOperand(1);
|
||||
@ -621,6 +732,55 @@ Id Builder::getContainedTypeId(Id typeId) const
|
||||
return getContainedTypeId(typeId, 0);
|
||||
}
|
||||
|
||||
// Returns true if 'typeId' is or contains a scalar type declared with 'typeOp'
|
||||
// of width 'width'. The 'width' is only consumed for int and float types.
|
||||
// Returns false otherwise.
|
||||
bool Builder::containsType(Id typeId, spv::Op typeOp, unsigned int width) const
|
||||
{
|
||||
const Instruction& instr = *module.getInstruction(typeId);
|
||||
|
||||
Op typeClass = instr.getOpCode();
|
||||
switch (typeClass)
|
||||
{
|
||||
case OpTypeInt:
|
||||
case OpTypeFloat:
|
||||
return typeClass == typeOp && instr.getImmediateOperand(0) == width;
|
||||
case OpTypeStruct:
|
||||
for (int m = 0; m < instr.getNumOperands(); ++m) {
|
||||
if (containsType(instr.getIdOperand(m), typeOp, width))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case OpTypePointer:
|
||||
return false;
|
||||
case OpTypeVector:
|
||||
case OpTypeMatrix:
|
||||
case OpTypeArray:
|
||||
case OpTypeRuntimeArray:
|
||||
return containsType(getContainedTypeId(typeId), typeOp, width);
|
||||
default:
|
||||
return typeClass == typeOp;
|
||||
}
|
||||
}
|
||||
|
||||
// return true if the type is a pointer to PhysicalStorageBufferEXT or an
|
||||
// array of such pointers. These require restrict/aliased decorations.
|
||||
bool Builder::containsPhysicalStorageBufferOrArray(Id typeId) const
|
||||
{
|
||||
const Instruction& instr = *module.getInstruction(typeId);
|
||||
|
||||
Op typeClass = instr.getOpCode();
|
||||
switch (typeClass)
|
||||
{
|
||||
case OpTypePointer:
|
||||
return getTypeStorageClass(typeId) == StorageClassPhysicalStorageBufferEXT;
|
||||
case OpTypeArray:
|
||||
return containsPhysicalStorageBufferOrArray(getContainedTypeId(typeId));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// See if a scalar constant of this type has already been created, so it
|
||||
// can be reused rather than duplicated. (Required by the specification).
|
||||
Id Builder::findScalarConstant(Op typeClass, Op opcode, Id typeId, unsigned value)
|
||||
@ -794,6 +954,10 @@ Id Builder::makeFloatConstant(float f, bool specConstant)
|
||||
|
||||
Id Builder::makeDoubleConstant(double d, bool specConstant)
|
||||
{
|
||||
#ifdef GLSLANG_WEB
|
||||
assert(0);
|
||||
return NoResult;
|
||||
#else
|
||||
Op opcode = specConstant ? OpSpecConstant : OpConstant;
|
||||
Id typeId = makeFloatType(64);
|
||||
union { double db; unsigned long long ull; } u;
|
||||
@ -818,10 +982,15 @@ Id Builder::makeDoubleConstant(double d, bool specConstant)
|
||||
module.mapInstruction(c);
|
||||
|
||||
return c->getResultId();
|
||||
#endif
|
||||
}
|
||||
|
||||
Id Builder::makeFloat16Constant(float f16, bool specConstant)
|
||||
{
|
||||
#ifdef GLSLANG_WEB
|
||||
assert(0);
|
||||
return NoResult;
|
||||
#else
|
||||
Op opcode = specConstant ? OpSpecConstant : OpConstant;
|
||||
Id typeId = makeFloatType(16);
|
||||
|
||||
@ -846,36 +1015,43 @@ Id Builder::makeFloat16Constant(float f16, bool specConstant)
|
||||
module.mapInstruction(c);
|
||||
|
||||
return c->getResultId();
|
||||
#endif
|
||||
}
|
||||
|
||||
Id Builder::makeFpConstant(Id type, double d, bool specConstant)
|
||||
{
|
||||
assert(isFloatType(type));
|
||||
#ifdef GLSLANG_WEB
|
||||
const int width = 32;
|
||||
assert(width == getScalarTypeWidth(type));
|
||||
#else
|
||||
const int width = getScalarTypeWidth(type);
|
||||
#endif
|
||||
|
||||
switch (getScalarTypeWidth(type)) {
|
||||
case 16:
|
||||
return makeFloat16Constant((float)d, specConstant);
|
||||
case 32:
|
||||
return makeFloatConstant((float)d, specConstant);
|
||||
case 64:
|
||||
return makeDoubleConstant(d, specConstant);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
assert(isFloatType(type));
|
||||
|
||||
assert(false);
|
||||
return NoResult;
|
||||
switch (width) {
|
||||
case 16:
|
||||
return makeFloat16Constant((float)d, specConstant);
|
||||
case 32:
|
||||
return makeFloatConstant((float)d, specConstant);
|
||||
case 64:
|
||||
return makeDoubleConstant(d, specConstant);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return NoResult;
|
||||
}
|
||||
|
||||
Id Builder::findCompositeConstant(Op typeClass, const std::vector<Id>& comps)
|
||||
Id Builder::findCompositeConstant(Op typeClass, Id typeId, const std::vector<Id>& comps)
|
||||
{
|
||||
Instruction* constant = 0;
|
||||
bool found = false;
|
||||
for (int i = 0; i < (int)groupedConstants[typeClass].size(); ++i) {
|
||||
constant = groupedConstants[typeClass][i];
|
||||
|
||||
// same shape?
|
||||
if (constant->getNumOperands() != (int)comps.size())
|
||||
if (constant->getTypeId() != typeId)
|
||||
continue;
|
||||
|
||||
// same contents?
|
||||
@ -930,8 +1106,9 @@ Id Builder::makeCompositeConstant(Id typeId, const std::vector<Id>& members, boo
|
||||
case OpTypeVector:
|
||||
case OpTypeArray:
|
||||
case OpTypeMatrix:
|
||||
case OpTypeCooperativeMatrixNV:
|
||||
if (! specConstant) {
|
||||
Id existing = findCompositeConstant(typeClass, members);
|
||||
Id existing = findCompositeConstant(typeClass, typeId, members);
|
||||
if (existing)
|
||||
return existing;
|
||||
}
|
||||
@ -1161,11 +1338,13 @@ void Builder::makeDiscard()
|
||||
}
|
||||
|
||||
// Comments in header
|
||||
Id Builder::createVariable(StorageClass storageClass, Id type, const char* name)
|
||||
Id Builder::createVariable(StorageClass storageClass, Id type, const char* name, Id initializer)
|
||||
{
|
||||
Id pointerType = makePointer(storageClass, type);
|
||||
Instruction* inst = new Instruction(getUniqueId(), pointerType, OpVariable);
|
||||
inst->addImmediateOperand(storageClass);
|
||||
if (initializer != NoResult)
|
||||
inst->addIdOperand(initializer);
|
||||
|
||||
switch (storageClass) {
|
||||
case StorageClassFunction:
|
||||
@ -1193,20 +1372,65 @@ Id Builder::createUndefined(Id type)
|
||||
return inst->getResultId();
|
||||
}
|
||||
|
||||
// av/vis/nonprivate are unnecessary and illegal for some storage classes.
|
||||
spv::MemoryAccessMask Builder::sanitizeMemoryAccessForStorageClass(spv::MemoryAccessMask memoryAccess, StorageClass sc) const
|
||||
{
|
||||
switch (sc) {
|
||||
case spv::StorageClassUniform:
|
||||
case spv::StorageClassWorkgroup:
|
||||
case spv::StorageClassStorageBuffer:
|
||||
case spv::StorageClassPhysicalStorageBufferEXT:
|
||||
break;
|
||||
default:
|
||||
memoryAccess = spv::MemoryAccessMask(memoryAccess &
|
||||
~(spv::MemoryAccessMakePointerAvailableKHRMask |
|
||||
spv::MemoryAccessMakePointerVisibleKHRMask |
|
||||
spv::MemoryAccessNonPrivatePointerKHRMask));
|
||||
break;
|
||||
}
|
||||
return memoryAccess;
|
||||
}
|
||||
|
||||
// Comments in header
|
||||
void Builder::createStore(Id rValue, Id lValue)
|
||||
void Builder::createStore(Id rValue, Id lValue, spv::MemoryAccessMask memoryAccess, spv::Scope scope, unsigned int alignment)
|
||||
{
|
||||
Instruction* store = new Instruction(OpStore);
|
||||
store->addIdOperand(lValue);
|
||||
store->addIdOperand(rValue);
|
||||
|
||||
memoryAccess = sanitizeMemoryAccessForStorageClass(memoryAccess, getStorageClass(lValue));
|
||||
|
||||
if (memoryAccess != MemoryAccessMaskNone) {
|
||||
store->addImmediateOperand(memoryAccess);
|
||||
if (memoryAccess & spv::MemoryAccessAlignedMask) {
|
||||
store->addImmediateOperand(alignment);
|
||||
}
|
||||
if (memoryAccess & spv::MemoryAccessMakePointerAvailableKHRMask) {
|
||||
store->addIdOperand(makeUintConstant(scope));
|
||||
}
|
||||
}
|
||||
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(store));
|
||||
}
|
||||
|
||||
// Comments in header
|
||||
Id Builder::createLoad(Id lValue)
|
||||
Id Builder::createLoad(Id lValue, spv::MemoryAccessMask memoryAccess, spv::Scope scope, unsigned int alignment)
|
||||
{
|
||||
Instruction* load = new Instruction(getUniqueId(), getDerefTypeId(lValue), OpLoad);
|
||||
load->addIdOperand(lValue);
|
||||
|
||||
memoryAccess = sanitizeMemoryAccessForStorageClass(memoryAccess, getStorageClass(lValue));
|
||||
|
||||
if (memoryAccess != MemoryAccessMaskNone) {
|
||||
load->addImmediateOperand(memoryAccess);
|
||||
if (memoryAccess & spv::MemoryAccessAlignedMask) {
|
||||
load->addImmediateOperand(alignment);
|
||||
}
|
||||
if (memoryAccess & spv::MemoryAccessMakePointerVisibleKHRMask) {
|
||||
load->addIdOperand(makeUintConstant(scope));
|
||||
}
|
||||
}
|
||||
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(load));
|
||||
|
||||
return load->getResultId();
|
||||
@ -1240,7 +1464,7 @@ Id Builder::createAccessChain(StorageClass storageClass, Id base, const std::vec
|
||||
|
||||
Id Builder::createArrayLength(Id base, unsigned int member)
|
||||
{
|
||||
spv::Id intType = makeIntType(32);
|
||||
spv::Id intType = makeUintType(32);
|
||||
Instruction* length = new Instruction(getUniqueId(), intType, OpArrayLength);
|
||||
length->addIdOperand(base);
|
||||
length->addImmediateOperand(member);
|
||||
@ -1249,6 +1473,23 @@ Id Builder::createArrayLength(Id base, unsigned int member)
|
||||
return length->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::createCooperativeMatrixLength(Id type)
|
||||
{
|
||||
spv::Id intType = makeUintType(32);
|
||||
|
||||
// Generate code for spec constants if in spec constant operation
|
||||
// generation mode.
|
||||
if (generatingOpCodeForSpecConst) {
|
||||
return createSpecConstantOp(OpCooperativeMatrixLengthNV, intType, std::vector<Id>(1, type), std::vector<Id>());
|
||||
}
|
||||
|
||||
Instruction* length = new Instruction(getUniqueId(), intType, OpCooperativeMatrixLengthNV);
|
||||
length->addIdOperand(type);
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(length));
|
||||
|
||||
return length->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::createCompositeExtract(Id composite, Id typeId, unsigned index)
|
||||
{
|
||||
// Generate code for spec constants if in spec constant operation
|
||||
@ -1331,7 +1572,7 @@ void Builder::createNoResultOp(Op opCode)
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
}
|
||||
|
||||
// An opcode that has one operand, no result id, and no type
|
||||
// An opcode that has one id operand, no result id, and no type
|
||||
void Builder::createNoResultOp(Op opCode, Id operand)
|
||||
{
|
||||
Instruction* op = new Instruction(opCode);
|
||||
@ -1339,29 +1580,43 @@ void Builder::createNoResultOp(Op opCode, Id operand)
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
}
|
||||
|
||||
// An opcode that has one operand, no result id, and no type
|
||||
// An opcode that has one or more operands, no result id, and no type
|
||||
void Builder::createNoResultOp(Op opCode, const std::vector<Id>& operands)
|
||||
{
|
||||
Instruction* op = new Instruction(opCode);
|
||||
for (auto it = operands.cbegin(); it != operands.cend(); ++it)
|
||||
for (auto it = operands.cbegin(); it != operands.cend(); ++it) {
|
||||
op->addIdOperand(*it);
|
||||
}
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
}
|
||||
|
||||
// An opcode that has multiple operands, no result id, and no type
|
||||
void Builder::createNoResultOp(Op opCode, const std::vector<IdImmediate>& operands)
|
||||
{
|
||||
Instruction* op = new Instruction(opCode);
|
||||
for (auto it = operands.cbegin(); it != operands.cend(); ++it) {
|
||||
if (it->isId)
|
||||
op->addIdOperand(it->word);
|
||||
else
|
||||
op->addImmediateOperand(it->word);
|
||||
}
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
}
|
||||
|
||||
void Builder::createControlBarrier(Scope execution, Scope memory, MemorySemanticsMask semantics)
|
||||
{
|
||||
Instruction* op = new Instruction(OpControlBarrier);
|
||||
op->addImmediateOperand(makeUintConstant(execution));
|
||||
op->addImmediateOperand(makeUintConstant(memory));
|
||||
op->addImmediateOperand(makeUintConstant(semantics));
|
||||
op->addIdOperand(makeUintConstant(execution));
|
||||
op->addIdOperand(makeUintConstant(memory));
|
||||
op->addIdOperand(makeUintConstant(semantics));
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
}
|
||||
|
||||
void Builder::createMemoryBarrier(unsigned executionScope, unsigned memorySemantics)
|
||||
{
|
||||
Instruction* op = new Instruction(OpMemoryBarrier);
|
||||
op->addImmediateOperand(makeUintConstant(executionScope));
|
||||
op->addImmediateOperand(makeUintConstant(memorySemantics));
|
||||
op->addIdOperand(makeUintConstant(executionScope));
|
||||
op->addIdOperand(makeUintConstant(memorySemantics));
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
}
|
||||
|
||||
@ -1428,6 +1683,20 @@ Id Builder::createOp(Op opCode, Id typeId, const std::vector<Id>& operands)
|
||||
return op->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::createOp(Op opCode, Id typeId, const std::vector<IdImmediate>& operands)
|
||||
{
|
||||
Instruction* op = new Instruction(getUniqueId(), typeId, opCode);
|
||||
for (auto it = operands.cbegin(); it != operands.cend(); ++it) {
|
||||
if (it->isId)
|
||||
op->addIdOperand(it->word);
|
||||
else
|
||||
op->addImmediateOperand(it->word);
|
||||
}
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
|
||||
return op->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::createSpecConstantOp(Op opCode, Id typeId, const std::vector<Id>& operands, const std::vector<unsigned>& literals)
|
||||
{
|
||||
Instruction* op = new Instruction(getUniqueId(), typeId, OpSpecConstantOp);
|
||||
@ -1570,7 +1839,8 @@ Id Builder::createBuiltinCall(Id resultType, Id builtins, int entryPoint, const
|
||||
|
||||
// Accept all parameters needed to create a texture instruction.
|
||||
// Create the correct instruction based on the inputs, and make the call.
|
||||
Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse, bool fetch, bool proj, bool gather, bool noImplicitLod, const TextureParameters& parameters)
|
||||
Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse, bool fetch, bool proj, bool gather,
|
||||
bool noImplicitLod, const TextureParameters& parameters, ImageOperandsMask signExtensionMask)
|
||||
{
|
||||
static const int maxTextureArgs = 10;
|
||||
Id texArgs[maxTextureArgs] = {};
|
||||
@ -1587,11 +1857,18 @@ Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse,
|
||||
if (parameters.component != NoResult)
|
||||
texArgs[numArgs++] = parameters.component;
|
||||
|
||||
#ifndef GLSLANG_WEB
|
||||
if (parameters.granularity != NoResult)
|
||||
texArgs[numArgs++] = parameters.granularity;
|
||||
if (parameters.coarse != NoResult)
|
||||
texArgs[numArgs++] = parameters.coarse;
|
||||
#endif
|
||||
|
||||
//
|
||||
// Set up the optional arguments
|
||||
//
|
||||
int optArgNum = numArgs; // track which operand, if it exists, is the mask of optional arguments
|
||||
++numArgs; // speculatively make room for the mask operand
|
||||
int optArgNum = numArgs; // track which operand, if it exists, is the mask of optional arguments
|
||||
++numArgs; // speculatively make room for the mask operand
|
||||
ImageOperandsMask mask = ImageOperandsMaskNone; // the mask operand
|
||||
if (parameters.bias) {
|
||||
mask = (ImageOperandsMask)(mask | ImageOperandsBiasMask);
|
||||
@ -1623,9 +1900,11 @@ Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse,
|
||||
texArgs[numArgs++] = parameters.offset;
|
||||
}
|
||||
if (parameters.offsets) {
|
||||
addCapability(CapabilityImageGatherExtended);
|
||||
mask = (ImageOperandsMask)(mask | ImageOperandsConstOffsetsMask);
|
||||
texArgs[numArgs++] = parameters.offsets;
|
||||
}
|
||||
#ifndef GLSLANG_WEB
|
||||
if (parameters.sample) {
|
||||
mask = (ImageOperandsMask)(mask | ImageOperandsSampleMask);
|
||||
texArgs[numArgs++] = parameters.sample;
|
||||
@ -1637,6 +1916,14 @@ Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse,
|
||||
mask = (ImageOperandsMask)(mask | ImageOperandsMinLodMask);
|
||||
texArgs[numArgs++] = parameters.lodClamp;
|
||||
}
|
||||
if (parameters.nonprivate) {
|
||||
mask = mask | ImageOperandsNonPrivateTexelKHRMask;
|
||||
}
|
||||
if (parameters.volatil) {
|
||||
mask = mask | ImageOperandsVolatileTexelKHRMask;
|
||||
}
|
||||
#endif
|
||||
mask = mask | signExtensionMask;
|
||||
if (mask == ImageOperandsMaskNone)
|
||||
--numArgs; // undo speculative reservation for the mask argument
|
||||
else
|
||||
@ -1651,6 +1938,9 @@ Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse,
|
||||
opCode = OpImageSparseFetch;
|
||||
else
|
||||
opCode = OpImageFetch;
|
||||
#ifndef GLSLANG_WEB
|
||||
} else if (parameters.granularity && parameters.coarse) {
|
||||
opCode = OpImageSampleFootprintNV;
|
||||
} else if (gather) {
|
||||
if (parameters.Dref)
|
||||
if (sparse)
|
||||
@ -1662,6 +1952,7 @@ Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse,
|
||||
opCode = OpImageSparseGather;
|
||||
else
|
||||
opCode = OpImageGather;
|
||||
#endif
|
||||
} else if (explicitLod) {
|
||||
if (parameters.Dref) {
|
||||
if (proj)
|
||||
@ -1772,9 +2063,6 @@ Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse,
|
||||
// Comments in header
|
||||
Id Builder::createTextureQueryCall(Op opCode, const TextureParameters& parameters, bool isUnsignedResult)
|
||||
{
|
||||
// All these need a capability
|
||||
addCapability(CapabilityImageQuery);
|
||||
|
||||
// Figure out the result type
|
||||
Id resultType = 0;
|
||||
switch (opCode) {
|
||||
@ -1813,11 +2101,7 @@ Id Builder::createTextureQueryCall(Op opCode, const TextureParameters& parameter
|
||||
break;
|
||||
}
|
||||
case OpImageQueryLod:
|
||||
#ifdef AMD_EXTENSIONS
|
||||
resultType = makeVectorType(getScalarTypeId(getTypeId(parameters.coords)), 2);
|
||||
#else
|
||||
resultType = makeVectorType(makeFloatType(32), 2);
|
||||
#endif
|
||||
break;
|
||||
case OpImageQueryLevels:
|
||||
case OpImageQuerySamples:
|
||||
@ -1835,6 +2119,7 @@ Id Builder::createTextureQueryCall(Op opCode, const TextureParameters& parameter
|
||||
if (parameters.lod)
|
||||
query->addIdOperand(parameters.lod);
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(query));
|
||||
addCapability(CapabilityImageQuery);
|
||||
|
||||
return query->getResultId();
|
||||
}
|
||||
@ -1999,7 +2284,8 @@ Id Builder::createConstructor(Decoration precision, const std::vector<Id>& sourc
|
||||
// Go through the source arguments, each one could have either
|
||||
// a single or multiple components to contribute.
|
||||
for (unsigned int i = 0; i < sources.size(); ++i) {
|
||||
if (isScalar(sources[i]))
|
||||
|
||||
if (isScalar(sources[i]) || isPointer(sources[i]))
|
||||
latchResult(sources[i]);
|
||||
else if (isVector(sources[i]))
|
||||
accumulateVectorConstituents(sources[i]);
|
||||
@ -2027,9 +2313,44 @@ Id Builder::createMatrixConstructor(Decoration precision, const std::vector<Id>&
|
||||
int numRows = getTypeNumRows(resultTypeId);
|
||||
|
||||
Instruction* instr = module.getInstruction(componentTypeId);
|
||||
Id bitCount = instr->getIdOperand(0);
|
||||
#ifdef GLSLANG_WEB
|
||||
const unsigned bitCount = 32;
|
||||
assert(bitCount == instr->getImmediateOperand(0));
|
||||
#else
|
||||
const unsigned bitCount = instr->getImmediateOperand(0);
|
||||
#endif
|
||||
|
||||
// Will use a two step process
|
||||
// Optimize matrix constructed from a bigger matrix
|
||||
if (isMatrix(sources[0]) && getNumColumns(sources[0]) >= numCols && getNumRows(sources[0]) >= numRows) {
|
||||
// To truncate the matrix to a smaller number of rows/columns, we need to:
|
||||
// 1. For each column, extract the column and truncate it to the required size using shuffle
|
||||
// 2. Assemble the resulting matrix from all columns
|
||||
Id matrix = sources[0];
|
||||
Id columnTypeId = getContainedTypeId(resultTypeId);
|
||||
Id sourceColumnTypeId = getContainedTypeId(getTypeId(matrix));
|
||||
|
||||
std::vector<unsigned> channels;
|
||||
for (int row = 0; row < numRows; ++row)
|
||||
channels.push_back(row);
|
||||
|
||||
std::vector<Id> matrixColumns;
|
||||
for (int col = 0; col < numCols; ++col) {
|
||||
std::vector<unsigned> indexes;
|
||||
indexes.push_back(col);
|
||||
Id colv = createCompositeExtract(matrix, sourceColumnTypeId, indexes);
|
||||
setPrecision(colv, precision);
|
||||
|
||||
if (numRows != getNumRows(matrix)) {
|
||||
matrixColumns.push_back(createRvalueSwizzle(precision, columnTypeId, colv, channels));
|
||||
} else {
|
||||
matrixColumns.push_back(colv);
|
||||
}
|
||||
}
|
||||
|
||||
return setPrecision(createCompositeConstruct(resultTypeId, matrixColumns), precision);
|
||||
}
|
||||
|
||||
// Otherwise, will use a two step process
|
||||
// 1. make a compile-time 2D array of values
|
||||
// 2. construct a matrix from that array
|
||||
|
||||
@ -2283,11 +2604,16 @@ void Builder::clearAccessChain()
|
||||
accessChain.component = NoResult;
|
||||
accessChain.preSwizzleBaseType = NoType;
|
||||
accessChain.isRValue = false;
|
||||
accessChain.coherentFlags.clear();
|
||||
accessChain.alignment = 0;
|
||||
}
|
||||
|
||||
// Comments in header
|
||||
void Builder::accessChainPushSwizzle(std::vector<unsigned>& swizzle, Id preSwizzleBaseType)
|
||||
void Builder::accessChainPushSwizzle(std::vector<unsigned>& swizzle, Id preSwizzleBaseType, AccessChain::CoherentFlags coherentFlags, unsigned int alignment)
|
||||
{
|
||||
accessChain.coherentFlags |= coherentFlags;
|
||||
accessChain.alignment |= alignment;
|
||||
|
||||
// swizzles can be stacked in GLSL, but simplified to a single
|
||||
// one here; the base type doesn't change
|
||||
if (accessChain.preSwizzleBaseType == NoType)
|
||||
@ -2309,7 +2635,7 @@ void Builder::accessChainPushSwizzle(std::vector<unsigned>& swizzle, Id preSwizz
|
||||
}
|
||||
|
||||
// Comments in header
|
||||
void Builder::accessChainStore(Id rvalue)
|
||||
void Builder::accessChainStore(Id rvalue, spv::MemoryAccessMask memoryAccess, spv::Scope scope, unsigned int alignment)
|
||||
{
|
||||
assert(accessChain.isRValue == false);
|
||||
|
||||
@ -2327,11 +2653,17 @@ void Builder::accessChainStore(Id rvalue)
|
||||
source = createLvalueSwizzle(getTypeId(tempBaseId), tempBaseId, source, accessChain.swizzle);
|
||||
}
|
||||
|
||||
createStore(source, base);
|
||||
// take LSB of alignment
|
||||
alignment = alignment & ~(alignment & (alignment-1));
|
||||
if (getStorageClass(base) == StorageClassPhysicalStorageBufferEXT) {
|
||||
memoryAccess = (spv::MemoryAccessMask)(memoryAccess | spv::MemoryAccessAlignedMask);
|
||||
}
|
||||
|
||||
createStore(source, base, memoryAccess, scope, alignment);
|
||||
}
|
||||
|
||||
// Comments in header
|
||||
Id Builder::accessChainLoad(Decoration precision, Decoration nonUniform, Id resultType)
|
||||
Id Builder::accessChainLoad(Decoration precision, Decoration nonUniform, Id resultType, spv::MemoryAccessMask memoryAccess, spv::Scope scope, unsigned int alignment)
|
||||
{
|
||||
Id id;
|
||||
|
||||
@ -2353,15 +2685,22 @@ Id Builder::accessChainLoad(Decoration precision, Decoration nonUniform, Id resu
|
||||
}
|
||||
}
|
||||
|
||||
if (constant)
|
||||
if (constant) {
|
||||
id = createCompositeExtract(accessChain.base, swizzleBase, indexes);
|
||||
else {
|
||||
// make a new function variable for this r-value
|
||||
Id lValue = createVariable(StorageClassFunction, getTypeId(accessChain.base), "indexable");
|
||||
|
||||
// store into it
|
||||
createStore(accessChain.base, lValue);
|
||||
|
||||
} else {
|
||||
Id lValue = NoResult;
|
||||
if (spvVersion >= Spv_1_4) {
|
||||
// make a new function variable for this r-value, using an initializer,
|
||||
// and mark it as NonWritable so that downstream it can be detected as a lookup
|
||||
// table
|
||||
lValue = createVariable(StorageClassFunction, getTypeId(accessChain.base), "indexable",
|
||||
accessChain.base);
|
||||
addDecoration(lValue, DecorationNonWritable);
|
||||
} else {
|
||||
lValue = createVariable(StorageClassFunction, getTypeId(accessChain.base), "indexable");
|
||||
// store into it
|
||||
createStore(accessChain.base, lValue);
|
||||
}
|
||||
// move base to the new variable
|
||||
accessChain.base = lValue;
|
||||
accessChain.isRValue = false;
|
||||
@ -2374,8 +2713,15 @@ Id Builder::accessChainLoad(Decoration precision, Decoration nonUniform, Id resu
|
||||
id = accessChain.base; // no precision, it was set when this was defined
|
||||
} else {
|
||||
transferAccessChainSwizzle(true);
|
||||
|
||||
// take LSB of alignment
|
||||
alignment = alignment & ~(alignment & (alignment-1));
|
||||
if (getStorageClass(accessChain.base) == StorageClassPhysicalStorageBufferEXT) {
|
||||
memoryAccess = (spv::MemoryAccessMask)(memoryAccess | spv::MemoryAccessAlignedMask);
|
||||
}
|
||||
|
||||
// load through the access chain
|
||||
id = createLoad(collapseAccessChain());
|
||||
id = createLoad(collapseAccessChain(), memoryAccess, scope, alignment);
|
||||
setPrecision(id, precision);
|
||||
addDecoration(id, nonUniform);
|
||||
}
|
||||
@ -2451,42 +2797,6 @@ Id Builder::accessChainGetInferredType()
|
||||
return type;
|
||||
}
|
||||
|
||||
// comment in header
|
||||
void Builder::eliminateDeadDecorations() {
|
||||
std::unordered_set<const Block*> reachable_blocks;
|
||||
std::unordered_set<Id> unreachable_definitions;
|
||||
// Collect IDs defined in unreachable blocks. For each function, label the
|
||||
// reachable blocks first. Then for each unreachable block, collect the
|
||||
// result IDs of the instructions in it.
|
||||
for (std::vector<Function*>::const_iterator fi = module.getFunctions().cbegin();
|
||||
fi != module.getFunctions().cend(); fi++) {
|
||||
Function* f = *fi;
|
||||
Block* entry = f->getEntryBlock();
|
||||
inReadableOrder(entry, [&reachable_blocks](const Block* b) {
|
||||
reachable_blocks.insert(b);
|
||||
});
|
||||
for (std::vector<Block*>::const_iterator bi = f->getBlocks().cbegin();
|
||||
bi != f->getBlocks().cend(); bi++) {
|
||||
Block* b = *bi;
|
||||
if (!reachable_blocks.count(b)) {
|
||||
for (std::vector<std::unique_ptr<Instruction> >::const_iterator
|
||||
ii = b->getInstructions().cbegin();
|
||||
ii != b->getInstructions().cend(); ii++) {
|
||||
Instruction* i = ii->get();
|
||||
unreachable_definitions.insert(i->getResultId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
decorations.erase(std::remove_if(decorations.begin(), decorations.end(),
|
||||
[&unreachable_definitions](std::unique_ptr<Instruction>& I) -> bool {
|
||||
Instruction* inst = I.get();
|
||||
Id decoration_id = inst->getIdOperand(0);
|
||||
return unreachable_definitions.count(decoration_id) != 0;
|
||||
}),
|
||||
decorations.end());
|
||||
}
|
||||
|
||||
void Builder::dump(std::vector<unsigned int>& out) const
|
||||
{
|
||||
// Header, before first instructions:
|
||||
@ -2692,14 +3002,14 @@ void Builder::createSelectionMerge(Block* mergeBlock, unsigned int control)
|
||||
}
|
||||
|
||||
void Builder::createLoopMerge(Block* mergeBlock, Block* continueBlock, unsigned int control,
|
||||
unsigned int dependencyLength)
|
||||
const std::vector<unsigned int>& operands)
|
||||
{
|
||||
Instruction* merge = new Instruction(OpLoopMerge);
|
||||
merge->addIdOperand(mergeBlock->getId());
|
||||
merge->addIdOperand(continueBlock->getId());
|
||||
merge->addImmediateOperand(control);
|
||||
if ((control & LoopControlDependencyLengthMask) != 0)
|
||||
merge->addImmediateOperand(dependencyLength);
|
||||
for (int op = 0; op < (int)operands.size(); ++op)
|
||||
merge->addImmediateOperand(operands[op]);
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(merge));
|
||||
}
|
||||
|
||||
@ -2717,7 +3027,8 @@ void Builder::createConditionalBranch(Id condition, Block* thenBlock, Block* els
|
||||
// OpSource
|
||||
// [OpSourceContinued]
|
||||
// ...
|
||||
void Builder::dumpSourceInstructions(std::vector<unsigned int>& out) const
|
||||
void Builder::dumpSourceInstructions(const spv::Id fileId, const std::string& text,
|
||||
std::vector<unsigned int>& out) const
|
||||
{
|
||||
const int maxWordCount = 0xFFFF;
|
||||
const int opSourceWordCount = 4;
|
||||
@ -2729,14 +3040,14 @@ void Builder::dumpSourceInstructions(std::vector<unsigned int>& out) const
|
||||
sourceInst.addImmediateOperand(source);
|
||||
sourceInst.addImmediateOperand(sourceVersion);
|
||||
// File operand
|
||||
if (sourceFileStringId != NoResult) {
|
||||
sourceInst.addIdOperand(sourceFileStringId);
|
||||
if (fileId != NoResult) {
|
||||
sourceInst.addIdOperand(fileId);
|
||||
// Source operand
|
||||
if (sourceText.size() > 0) {
|
||||
if (text.size() > 0) {
|
||||
int nextByte = 0;
|
||||
std::string subString;
|
||||
while ((int)sourceText.size() - nextByte > 0) {
|
||||
subString = sourceText.substr(nextByte, nonNullBytesPerInstruction);
|
||||
while ((int)text.size() - nextByte > 0) {
|
||||
subString = text.substr(nextByte, nonNullBytesPerInstruction);
|
||||
if (nextByte == 0) {
|
||||
// OpSource
|
||||
sourceInst.addStringOperand(subString.c_str());
|
||||
@ -2756,6 +3067,14 @@ void Builder::dumpSourceInstructions(std::vector<unsigned int>& out) const
|
||||
}
|
||||
}
|
||||
|
||||
// Dump an OpSource[Continued] sequence for the source and every include file
|
||||
void Builder::dumpSourceInstructions(std::vector<unsigned int>& out) const
|
||||
{
|
||||
dumpSourceInstructions(sourceFileStringId, sourceText, out);
|
||||
for (auto iItr = includeFiles.begin(); iItr != includeFiles.end(); ++iItr)
|
||||
dumpSourceInstructions(iItr->first, *iItr->second, out);
|
||||
}
|
||||
|
||||
void Builder::dumpInstructions(std::vector<unsigned int>& out, const std::vector<std::unique_ptr<Instruction> >& instructions) const
|
||||
{
|
||||
for (int i = 0; i < (int)instructions.size(); ++i) {
|
||||
|
Reference in New Issue
Block a user