// smol-v - public domain - https://github.com/aras-p/smol-v
// authored 2016-2024 by Aras Pranckevicius
// no warranty implied; use at your own risk
// See end of file for license information.

#include "smolv.h"
#include <stdint.h>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstring>

#if !defined(_MSC_VER) && __cplusplus < 201103L
#define static_assert(x,y)
#endif

#define _SMOLV_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))

// --------------------------------------------------------------------------------------------
// Metadata about known SPIR-V operations

enum SpvOp
{
	SpvOpNop = 0,
	SpvOpUndef = 1,
	SpvOpSourceContinued = 2,
	SpvOpSource = 3,
	SpvOpSourceExtension = 4,
	SpvOpName = 5,
	SpvOpMemberName = 6,
	SpvOpString = 7,
	SpvOpLine = 8,
	SpvOpExtension = 10,
	SpvOpExtInstImport = 11,
	SpvOpExtInst = 12,
	SpvOpVectorShuffleCompact = 13, // not in SPIR-V, added for SMOL-V!
	SpvOpMemoryModel = 14,
	SpvOpEntryPoint = 15,
	SpvOpExecutionMode = 16,
	SpvOpCapability = 17,
	SpvOpTypeVoid = 19,
	SpvOpTypeBool = 20,
	SpvOpTypeInt = 21,
	SpvOpTypeFloat = 22,
	SpvOpTypeVector = 23,
	SpvOpTypeMatrix = 24,
	SpvOpTypeImage = 25,
	SpvOpTypeSampler = 26,
	SpvOpTypeSampledImage = 27,
	SpvOpTypeArray = 28,
	SpvOpTypeRuntimeArray = 29,
	SpvOpTypeStruct = 30,
	SpvOpTypeOpaque = 31,
	SpvOpTypePointer = 32,
	SpvOpTypeFunction = 33,
	SpvOpTypeEvent = 34,
	SpvOpTypeDeviceEvent = 35,
	SpvOpTypeReserveId = 36,
	SpvOpTypeQueue = 37,
	SpvOpTypePipe = 38,
	SpvOpTypeForwardPointer = 39,
	SpvOpConstantTrue = 41,
	SpvOpConstantFalse = 42,
	SpvOpConstant = 43,
	SpvOpConstantComposite = 44,
	SpvOpConstantSampler = 45,
	SpvOpConstantNull = 46,
	SpvOpSpecConstantTrue = 48,
	SpvOpSpecConstantFalse = 49,
	SpvOpSpecConstant = 50,
	SpvOpSpecConstantComposite = 51,
	SpvOpSpecConstantOp = 52,
	SpvOpFunction = 54,
	SpvOpFunctionParameter = 55,
	SpvOpFunctionEnd = 56,
	SpvOpFunctionCall = 57,
	SpvOpVariable = 59,
	SpvOpImageTexelPointer = 60,
	SpvOpLoad = 61,
	SpvOpStore = 62,
	SpvOpCopyMemory = 63,
	SpvOpCopyMemorySized = 64,
	SpvOpAccessChain = 65,
	SpvOpInBoundsAccessChain = 66,
	SpvOpPtrAccessChain = 67,
	SpvOpArrayLength = 68,
	SpvOpGenericPtrMemSemantics = 69,
	SpvOpInBoundsPtrAccessChain = 70,
	SpvOpDecorate = 71,
	SpvOpMemberDecorate = 72,
	SpvOpDecorationGroup = 73,
	SpvOpGroupDecorate = 74,
	SpvOpGroupMemberDecorate = 75,
	SpvOpVectorExtractDynamic = 77,
	SpvOpVectorInsertDynamic = 78,
	SpvOpVectorShuffle = 79,
	SpvOpCompositeConstruct = 80,
	SpvOpCompositeExtract = 81,
	SpvOpCompositeInsert = 82,
	SpvOpCopyObject = 83,
	SpvOpTranspose = 84,
	SpvOpSampledImage = 86,
	SpvOpImageSampleImplicitLod = 87,
	SpvOpImageSampleExplicitLod = 88,
	SpvOpImageSampleDrefImplicitLod = 89,
	SpvOpImageSampleDrefExplicitLod = 90,
	SpvOpImageSampleProjImplicitLod = 91,
	SpvOpImageSampleProjExplicitLod = 92,
	SpvOpImageSampleProjDrefImplicitLod = 93,
	SpvOpImageSampleProjDrefExplicitLod = 94,
	SpvOpImageFetch = 95,
	SpvOpImageGather = 96,
	SpvOpImageDrefGather = 97,
	SpvOpImageRead = 98,
	SpvOpImageWrite = 99,
	SpvOpImage = 100,
	SpvOpImageQueryFormat = 101,
	SpvOpImageQueryOrder = 102,
	SpvOpImageQuerySizeLod = 103,
	SpvOpImageQuerySize = 104,
	SpvOpImageQueryLod = 105,
	SpvOpImageQueryLevels = 106,
	SpvOpImageQuerySamples = 107,
	SpvOpConvertFToU = 109,
	SpvOpConvertFToS = 110,
	SpvOpConvertSToF = 111,
	SpvOpConvertUToF = 112,
	SpvOpUConvert = 113,
	SpvOpSConvert = 114,
	SpvOpFConvert = 115,
	SpvOpQuantizeToF16 = 116,
	SpvOpConvertPtrToU = 117,
	SpvOpSatConvertSToU = 118,
	SpvOpSatConvertUToS = 119,
	SpvOpConvertUToPtr = 120,
	SpvOpPtrCastToGeneric = 121,
	SpvOpGenericCastToPtr = 122,
	SpvOpGenericCastToPtrExplicit = 123,
	SpvOpBitcast = 124,
	SpvOpSNegate = 126,
	SpvOpFNegate = 127,
	SpvOpIAdd = 128,
	SpvOpFAdd = 129,
	SpvOpISub = 130,
	SpvOpFSub = 131,
	SpvOpIMul = 132,
	SpvOpFMul = 133,
	SpvOpUDiv = 134,
	SpvOpSDiv = 135,
	SpvOpFDiv = 136,
	SpvOpUMod = 137,
	SpvOpSRem = 138,
	SpvOpSMod = 139,
	SpvOpFRem = 140,
	SpvOpFMod = 141,
	SpvOpVectorTimesScalar = 142,
	SpvOpMatrixTimesScalar = 143,
	SpvOpVectorTimesMatrix = 144,
	SpvOpMatrixTimesVector = 145,
	SpvOpMatrixTimesMatrix = 146,
	SpvOpOuterProduct = 147,
	SpvOpDot = 148,
	SpvOpIAddCarry = 149,
	SpvOpISubBorrow = 150,
	SpvOpUMulExtended = 151,
	SpvOpSMulExtended = 152,
	SpvOpAny = 154,
	SpvOpAll = 155,
	SpvOpIsNan = 156,
	SpvOpIsInf = 157,
	SpvOpIsFinite = 158,
	SpvOpIsNormal = 159,
	SpvOpSignBitSet = 160,
	SpvOpLessOrGreater = 161,
	SpvOpOrdered = 162,
	SpvOpUnordered = 163,
	SpvOpLogicalEqual = 164,
	SpvOpLogicalNotEqual = 165,
	SpvOpLogicalOr = 166,
	SpvOpLogicalAnd = 167,
	SpvOpLogicalNot = 168,
	SpvOpSelect = 169,
	SpvOpIEqual = 170,
	SpvOpINotEqual = 171,
	SpvOpUGreaterThan = 172,
	SpvOpSGreaterThan = 173,
	SpvOpUGreaterThanEqual = 174,
	SpvOpSGreaterThanEqual = 175,
	SpvOpULessThan = 176,
	SpvOpSLessThan = 177,
	SpvOpULessThanEqual = 178,
	SpvOpSLessThanEqual = 179,
	SpvOpFOrdEqual = 180,
	SpvOpFUnordEqual = 181,
	SpvOpFOrdNotEqual = 182,
	SpvOpFUnordNotEqual = 183,
	SpvOpFOrdLessThan = 184,
	SpvOpFUnordLessThan = 185,
	SpvOpFOrdGreaterThan = 186,
	SpvOpFUnordGreaterThan = 187,
	SpvOpFOrdLessThanEqual = 188,
	SpvOpFUnordLessThanEqual = 189,
	SpvOpFOrdGreaterThanEqual = 190,
	SpvOpFUnordGreaterThanEqual = 191,
	SpvOpShiftRightLogical = 194,
	SpvOpShiftRightArithmetic = 195,
	SpvOpShiftLeftLogical = 196,
	SpvOpBitwiseOr = 197,
	SpvOpBitwiseXor = 198,
	SpvOpBitwiseAnd = 199,
	SpvOpNot = 200,
	SpvOpBitFieldInsert = 201,
	SpvOpBitFieldSExtract = 202,
	SpvOpBitFieldUExtract = 203,
	SpvOpBitReverse = 204,
	SpvOpBitCount = 205,
	SpvOpDPdx = 207,
	SpvOpDPdy = 208,
	SpvOpFwidth = 209,
	SpvOpDPdxFine = 210,
	SpvOpDPdyFine = 211,
	SpvOpFwidthFine = 212,
	SpvOpDPdxCoarse = 213,
	SpvOpDPdyCoarse = 214,
	SpvOpFwidthCoarse = 215,
	SpvOpEmitVertex = 218,
	SpvOpEndPrimitive = 219,
	SpvOpEmitStreamVertex = 220,
	SpvOpEndStreamPrimitive = 221,
	SpvOpControlBarrier = 224,
	SpvOpMemoryBarrier = 225,
	SpvOpAtomicLoad = 227,
	SpvOpAtomicStore = 228,
	SpvOpAtomicExchange = 229,
	SpvOpAtomicCompareExchange = 230,
	SpvOpAtomicCompareExchangeWeak = 231,
	SpvOpAtomicIIncrement = 232,
	SpvOpAtomicIDecrement = 233,
	SpvOpAtomicIAdd = 234,
	SpvOpAtomicISub = 235,
	SpvOpAtomicSMin = 236,
	SpvOpAtomicUMin = 237,
	SpvOpAtomicSMax = 238,
	SpvOpAtomicUMax = 239,
	SpvOpAtomicAnd = 240,
	SpvOpAtomicOr = 241,
	SpvOpAtomicXor = 242,
	SpvOpPhi = 245,
	SpvOpLoopMerge = 246,
	SpvOpSelectionMerge = 247,
	SpvOpLabel = 248,
	SpvOpBranch = 249,
	SpvOpBranchConditional = 250,
	SpvOpSwitch = 251,
	SpvOpKill = 252,
	SpvOpReturn = 253,
	SpvOpReturnValue = 254,
	SpvOpUnreachable = 255,
	SpvOpLifetimeStart = 256,
	SpvOpLifetimeStop = 257,
	SpvOpGroupAsyncCopy = 259,
	SpvOpGroupWaitEvents = 260,
	SpvOpGroupAll = 261,
	SpvOpGroupAny = 262,
	SpvOpGroupBroadcast = 263,
	SpvOpGroupIAdd = 264,
	SpvOpGroupFAdd = 265,
	SpvOpGroupFMin = 266,
	SpvOpGroupUMin = 267,
	SpvOpGroupSMin = 268,
	SpvOpGroupFMax = 269,
	SpvOpGroupUMax = 270,
	SpvOpGroupSMax = 271,
	SpvOpReadPipe = 274,
	SpvOpWritePipe = 275,
	SpvOpReservedReadPipe = 276,
	SpvOpReservedWritePipe = 277,
	SpvOpReserveReadPipePackets = 278,
	SpvOpReserveWritePipePackets = 279,
	SpvOpCommitReadPipe = 280,
	SpvOpCommitWritePipe = 281,
	SpvOpIsValidReserveId = 282,
	SpvOpGetNumPipePackets = 283,
	SpvOpGetMaxPipePackets = 284,
	SpvOpGroupReserveReadPipePackets = 285,
	SpvOpGroupReserveWritePipePackets = 286,
	SpvOpGroupCommitReadPipe = 287,
	SpvOpGroupCommitWritePipe = 288,
	SpvOpEnqueueMarker = 291,
	SpvOpEnqueueKernel = 292,
	SpvOpGetKernelNDrangeSubGroupCount = 293,
	SpvOpGetKernelNDrangeMaxSubGroupSize = 294,
	SpvOpGetKernelWorkGroupSize = 295,
	SpvOpGetKernelPreferredWorkGroupSizeMultiple = 296,
	SpvOpRetainEvent = 297,
	SpvOpReleaseEvent = 298,
	SpvOpCreateUserEvent = 299,
	SpvOpIsValidEvent = 300,
	SpvOpSetUserEventStatus = 301,
	SpvOpCaptureEventProfilingInfo = 302,
	SpvOpGetDefaultQueue = 303,
	SpvOpBuildNDRange = 304,
	SpvOpImageSparseSampleImplicitLod = 305,
	SpvOpImageSparseSampleExplicitLod = 306,
	SpvOpImageSparseSampleDrefImplicitLod = 307,
	SpvOpImageSparseSampleDrefExplicitLod = 308,
	SpvOpImageSparseSampleProjImplicitLod = 309,
	SpvOpImageSparseSampleProjExplicitLod = 310,
	SpvOpImageSparseSampleProjDrefImplicitLod = 311,
	SpvOpImageSparseSampleProjDrefExplicitLod = 312,
	SpvOpImageSparseFetch = 313,
	SpvOpImageSparseGather = 314,
	SpvOpImageSparseDrefGather = 315,
	SpvOpImageSparseTexelsResident = 316,
	SpvOpNoLine = 317,
	SpvOpAtomicFlagTestAndSet = 318,
	SpvOpAtomicFlagClear = 319,
	SpvOpImageSparseRead = 320,
	SpvOpSizeOf = 321,
	SpvOpTypePipeStorage = 322,
	SpvOpConstantPipeStorage = 323,
	SpvOpCreatePipeFromPipeStorage = 324,
	SpvOpGetKernelLocalSizeForSubgroupCount = 325,
	SpvOpGetKernelMaxNumSubgroups = 326,
	SpvOpTypeNamedBarrier = 327,
	SpvOpNamedBarrierInitialize = 328,
	SpvOpMemoryNamedBarrier = 329,
	SpvOpModuleProcessed = 330,
	SpvOpExecutionModeId = 331,
	SpvOpDecorateId = 332,
	SpvOpGroupNonUniformElect = 333,
	SpvOpGroupNonUniformAll = 334,
	SpvOpGroupNonUniformAny = 335,
	SpvOpGroupNonUniformAllEqual = 336,
	SpvOpGroupNonUniformBroadcast = 337,
	SpvOpGroupNonUniformBroadcastFirst = 338,
	SpvOpGroupNonUniformBallot = 339,
	SpvOpGroupNonUniformInverseBallot = 340,
	SpvOpGroupNonUniformBallotBitExtract = 341,
	SpvOpGroupNonUniformBallotBitCount = 342,
	SpvOpGroupNonUniformBallotFindLSB = 343,
	SpvOpGroupNonUniformBallotFindMSB = 344,
	SpvOpGroupNonUniformShuffle = 345,
	SpvOpGroupNonUniformShuffleXor = 346,
	SpvOpGroupNonUniformShuffleUp = 347,
	SpvOpGroupNonUniformShuffleDown = 348,
	SpvOpGroupNonUniformIAdd = 349,
	SpvOpGroupNonUniformFAdd = 350,
	SpvOpGroupNonUniformIMul = 351,
	SpvOpGroupNonUniformFMul = 352,
	SpvOpGroupNonUniformSMin = 353,
	SpvOpGroupNonUniformUMin = 354,
	SpvOpGroupNonUniformFMin = 355,
	SpvOpGroupNonUniformSMax = 356,
	SpvOpGroupNonUniformUMax = 357,
	SpvOpGroupNonUniformFMax = 358,
	SpvOpGroupNonUniformBitwiseAnd = 359,
	SpvOpGroupNonUniformBitwiseOr = 360,
	SpvOpGroupNonUniformBitwiseXor = 361,
	SpvOpGroupNonUniformLogicalAnd = 362,
	SpvOpGroupNonUniformLogicalOr = 363,
	SpvOpGroupNonUniformLogicalXor = 364,
	SpvOpGroupNonUniformQuadBroadcast = 365,
	SpvOpGroupNonUniformQuadSwap = 366,
};
static const int kKnownOpsCount = SpvOpGroupNonUniformQuadSwap+1;


static const char* kSpirvOpNames[] =
{
	"Nop",
	"Undef",
	"SourceContinued",
	"Source",
	"SourceExtension",
	"Name",
	"MemberName",
	"String",
	"Line",
	"#9",
	"Extension",
	"ExtInstImport",
	"ExtInst",
	"VectorShuffleCompact",
	"MemoryModel",
	"EntryPoint",
	"ExecutionMode",
	"Capability",
	"#18",
	"TypeVoid",
	"TypeBool",
	"TypeInt",
	"TypeFloat",
	"TypeVector",
	"TypeMatrix",
	"TypeImage",
	"TypeSampler",
	"TypeSampledImage",
	"TypeArray",
	"TypeRuntimeArray",
	"TypeStruct",
	"TypeOpaque",
	"TypePointer",
	"TypeFunction",
	"TypeEvent",
	"TypeDeviceEvent",
	"TypeReserveId",
	"TypeQueue",
	"TypePipe",
	"TypeForwardPointer",
	"#40",
	"ConstantTrue",
	"ConstantFalse",
	"Constant",
	"ConstantComposite",
	"ConstantSampler",
	"ConstantNull",
	"#47",
	"SpecConstantTrue",
	"SpecConstantFalse",
	"SpecConstant",
	"SpecConstantComposite",
	"SpecConstantOp",
	"#53",
	"Function",
	"FunctionParameter",
	"FunctionEnd",
	"FunctionCall",
	"#58",
	"Variable",
	"ImageTexelPointer",
	"Load",
	"Store",
	"CopyMemory",
	"CopyMemorySized",
	"AccessChain",
	"InBoundsAccessChain",
	"PtrAccessChain",
	"ArrayLength",
	"GenericPtrMemSemantics",
	"InBoundsPtrAccessChain",
	"Decorate",
	"MemberDecorate",
	"DecorationGroup",
	"GroupDecorate",
	"GroupMemberDecorate",
	"#76",
	"VectorExtractDynamic",
	"VectorInsertDynamic",
	"VectorShuffle",
	"CompositeConstruct",
	"CompositeExtract",
	"CompositeInsert",
	"CopyObject",
	"Transpose",
	"#85",
	"SampledImage",
	"ImageSampleImplicitLod",
	"ImageSampleExplicitLod",
	"ImageSampleDrefImplicitLod",
	"ImageSampleDrefExplicitLod",
	"ImageSampleProjImplicitLod",
	"ImageSampleProjExplicitLod",
	"ImageSampleProjDrefImplicitLod",
	"ImageSampleProjDrefExplicitLod",
	"ImageFetch",
	"ImageGather",
	"ImageDrefGather",
	"ImageRead",
	"ImageWrite",
	"Image",
	"ImageQueryFormat",
	"ImageQueryOrder",
	"ImageQuerySizeLod",
	"ImageQuerySize",
	"ImageQueryLod",
	"ImageQueryLevels",
	"ImageQuerySamples",
	"#108",
	"ConvertFToU",
	"ConvertFToS",
	"ConvertSToF",
	"ConvertUToF",
	"UConvert",
	"SConvert",
	"FConvert",
	"QuantizeToF16",
	"ConvertPtrToU",
	"SatConvertSToU",
	"SatConvertUToS",
	"ConvertUToPtr",
	"PtrCastToGeneric",
	"GenericCastToPtr",
	"GenericCastToPtrExplicit",
	"Bitcast",
	"#125",
	"SNegate",
	"FNegate",
	"IAdd",
	"FAdd",
	"ISub",
	"FSub",
	"IMul",
	"FMul",
	"UDiv",
	"SDiv",
	"FDiv",
	"UMod",
	"SRem",
	"SMod",
	"FRem",
	"FMod",
	"VectorTimesScalar",
	"MatrixTimesScalar",
	"VectorTimesMatrix",
	"MatrixTimesVector",
	"MatrixTimesMatrix",
	"OuterProduct",
	"Dot",
	"IAddCarry",
	"ISubBorrow",
	"UMulExtended",
	"SMulExtended",
	"#153",
	"Any",
	"All",
	"IsNan",
	"IsInf",
	"IsFinite",
	"IsNormal",
	"SignBitSet",
	"LessOrGreater",
	"Ordered",
	"Unordered",
	"LogicalEqual",
	"LogicalNotEqual",
	"LogicalOr",
	"LogicalAnd",
	"LogicalNot",
	"Select",
	"IEqual",
	"INotEqual",
	"UGreaterThan",
	"SGreaterThan",
	"UGreaterThanEqual",
	"SGreaterThanEqual",
	"ULessThan",
	"SLessThan",
	"ULessThanEqual",
	"SLessThanEqual",
	"FOrdEqual",
	"FUnordEqual",
	"FOrdNotEqual",
	"FUnordNotEqual",
	"FOrdLessThan",
	"FUnordLessThan",
	"FOrdGreaterThan",
	"FUnordGreaterThan",
	"FOrdLessThanEqual",
	"FUnordLessThanEqual",
	"FOrdGreaterThanEqual",
	"FUnordGreaterThanEqual",
	"#192",
	"#193",
	"ShiftRightLogical",
	"ShiftRightArithmetic",
	"ShiftLeftLogical",
	"BitwiseOr",
	"BitwiseXor",
	"BitwiseAnd",
	"Not",
	"BitFieldInsert",
	"BitFieldSExtract",
	"BitFieldUExtract",
	"BitReverse",
	"BitCount",
	"#206",
	"DPdx",
	"DPdy",
	"Fwidth",
	"DPdxFine",
	"DPdyFine",
	"FwidthFine",
	"DPdxCoarse",
	"DPdyCoarse",
	"FwidthCoarse",
	"#216",
	"#217",
	"EmitVertex",
	"EndPrimitive",
	"EmitStreamVertex",
	"EndStreamPrimitive",
	"#222",
	"#223",
	"ControlBarrier",
	"MemoryBarrier",
	"#226",
	"AtomicLoad",
	"AtomicStore",
	"AtomicExchange",
	"AtomicCompareExchange",
	"AtomicCompareExchangeWeak",
	"AtomicIIncrement",
	"AtomicIDecrement",
	"AtomicIAdd",
	"AtomicISub",
	"AtomicSMin",
	"AtomicUMin",
	"AtomicSMax",
	"AtomicUMax",
	"AtomicAnd",
	"AtomicOr",
	"AtomicXor",
	"#243",
	"#244",
	"Phi",
	"LoopMerge",
	"SelectionMerge",
	"Label",
	"Branch",
	"BranchConditional",
	"Switch",
	"Kill",
	"Return",
	"ReturnValue",
	"Unreachable",
	"LifetimeStart",
	"LifetimeStop",
	"#258",
	"GroupAsyncCopy",
	"GroupWaitEvents",
	"GroupAll",
	"GroupAny",
	"GroupBroadcast",
	"GroupIAdd",
	"GroupFAdd",
	"GroupFMin",
	"GroupUMin",
	"GroupSMin",
	"GroupFMax",
	"GroupUMax",
	"GroupSMax",
	"#272",
	"#273",
	"ReadPipe",
	"WritePipe",
	"ReservedReadPipe",
	"ReservedWritePipe",
	"ReserveReadPipePackets",
	"ReserveWritePipePackets",
	"CommitReadPipe",
	"CommitWritePipe",
	"IsValidReserveId",
	"GetNumPipePackets",
	"GetMaxPipePackets",
	"GroupReserveReadPipePackets",
	"GroupReserveWritePipePackets",
	"GroupCommitReadPipe",
	"GroupCommitWritePipe",
	"#289",
	"#290",
	"EnqueueMarker",
	"EnqueueKernel",
	"GetKernelNDrangeSubGroupCount",
	"GetKernelNDrangeMaxSubGroupSize",
	"GetKernelWorkGroupSize",
	"GetKernelPreferredWorkGroupSizeMultiple",
	"RetainEvent",
	"ReleaseEvent",
	"CreateUserEvent",
	"IsValidEvent",
	"SetUserEventStatus",
	"CaptureEventProfilingInfo",
	"GetDefaultQueue",
	"BuildNDRange",
	"ImageSparseSampleImplicitLod",
	"ImageSparseSampleExplicitLod",
	"ImageSparseSampleDrefImplicitLod",
	"ImageSparseSampleDrefExplicitLod",
	"ImageSparseSampleProjImplicitLod",
	"ImageSparseSampleProjExplicitLod",
	"ImageSparseSampleProjDrefImplicitLod",
	"ImageSparseSampleProjDrefExplicitLod",
	"ImageSparseFetch",
	"ImageSparseGather",
	"ImageSparseDrefGather",
	"ImageSparseTexelsResident",
	"NoLine",
	"AtomicFlagTestAndSet",
	"AtomicFlagClear",
	"ImageSparseRead",
	"SizeOf",
	"TypePipeStorage",
	"ConstantPipeStorage",
	"CreatePipeFromPipeStorage",
	"GetKernelLocalSizeForSubgroupCount",
	"GetKernelMaxNumSubgroups",
	"TypeNamedBarrier",
	"NamedBarrierInitialize",
	"MemoryNamedBarrier",
	"ModuleProcessed",
	"ExecutionModeId",
	"DecorateId",
	"GroupNonUniformElect",
	"GroupNonUniformAll",
	"GroupNonUniformAny",
	"GroupNonUniformAllEqual",
	"GroupNonUniformBroadcast",
	"GroupNonUniformBroadcastFirst",
	"GroupNonUniformBallot",
	"GroupNonUniformInverseBallot",
	"GroupNonUniformBallotBitExtract",
	"GroupNonUniformBallotBitCount",
	"GroupNonUniformBallotFindLSB",
	"GroupNonUniformBallotFindMSB",
	"GroupNonUniformShuffle",
	"GroupNonUniformShuffleXor",
	"GroupNonUniformShuffleUp",
	"GroupNonUniformShuffleDown",
	"GroupNonUniformIAdd",
	"GroupNonUniformFAdd",
	"GroupNonUniformIMul",
	"GroupNonUniformFMul",
	"GroupNonUniformSMin",
	"GroupNonUniformUMin",
	"GroupNonUniformFMin",
	"GroupNonUniformSMax",
	"GroupNonUniformUMax",
	"GroupNonUniformFMax",
	"GroupNonUniformBitwiseAnd",
	"GroupNonUniformBitwiseOr",
	"GroupNonUniformBitwiseXor",
	"GroupNonUniformLogicalAnd",
	"GroupNonUniformLogicalOr",
	"GroupNonUniformLogicalXor",
	"GroupNonUniformQuadBroadcast",
	"GroupNonUniformQuadSwap",
};
static_assert(_SMOLV_ARRAY_SIZE(kSpirvOpNames) == kKnownOpsCount, "kSpirvOpNames table mismatch with known SpvOps");


struct OpData
{
	uint8_t hasResult;	// does it have result ID?
	uint8_t hasType;	// does it have type ID?
	uint8_t deltaFromResult; // How many words after (optional) type+result to write out as deltas from result?
	uint8_t varrest;	// should the rest of words be written in varint encoding?
};
static const OpData kSpirvOpData[] =
{
	{0, 0, 0, 0}, // Nop
	{1, 1, 0, 0}, // Undef
	{0, 0, 0, 0}, // SourceContinued
	{0, 0, 0, 1}, // Source
	{0, 0, 0, 0}, // SourceExtension
	{0, 0, 0, 0}, // Name
	{0, 0, 0, 0}, // MemberName
	{0, 0, 0, 0}, // String
	{0, 0, 0, 1}, // Line
	{1, 1, 0, 0}, // #9
	{0, 0, 0, 0}, // Extension
	{1, 0, 0, 0}, // ExtInstImport
	{1, 1, 0, 1}, // ExtInst
	{1, 1, 2, 1}, // VectorShuffleCompact - new in SMOLV
	{0, 0, 0, 1}, // MemoryModel
	{0, 0, 0, 1}, // EntryPoint
	{0, 0, 0, 1}, // ExecutionMode
	{0, 0, 0, 1}, // Capability
	{1, 1, 0, 0}, // #18
	{1, 0, 0, 1}, // TypeVoid
	{1, 0, 0, 1}, // TypeBool
	{1, 0, 0, 1}, // TypeInt
	{1, 0, 0, 1}, // TypeFloat
	{1, 0, 0, 1}, // TypeVector
	{1, 0, 0, 1}, // TypeMatrix
	{1, 0, 0, 1}, // TypeImage
	{1, 0, 0, 1}, // TypeSampler
	{1, 0, 0, 1}, // TypeSampledImage
	{1, 0, 0, 1}, // TypeArray
	{1, 0, 0, 1}, // TypeRuntimeArray
	{1, 0, 0, 1}, // TypeStruct
	{1, 0, 0, 1}, // TypeOpaque
	{1, 0, 0, 1}, // TypePointer
	{1, 0, 0, 1}, // TypeFunction
	{1, 0, 0, 1}, // TypeEvent
	{1, 0, 0, 1}, // TypeDeviceEvent
	{1, 0, 0, 1}, // TypeReserveId
	{1, 0, 0, 1}, // TypeQueue
	{1, 0, 0, 1}, // TypePipe
	{0, 0, 0, 1}, // TypeForwardPointer
	{1, 1, 0, 0}, // #40
	{1, 1, 0, 0}, // ConstantTrue
	{1, 1, 0, 0}, // ConstantFalse
	{1, 1, 0, 0}, // Constant
	{1, 1, 9, 0}, // ConstantComposite
	{1, 1, 0, 1}, // ConstantSampler
	{1, 1, 0, 0}, // ConstantNull
	{1, 1, 0, 0}, // #47
	{1, 1, 0, 0}, // SpecConstantTrue
	{1, 1, 0, 0}, // SpecConstantFalse
	{1, 1, 0, 0}, // SpecConstant
	{1, 1, 9, 0}, // SpecConstantComposite
	{1, 1, 0, 0}, // SpecConstantOp
	{1, 1, 0, 0}, // #53
	{1, 1, 0, 1}, // Function
	{1, 1, 0, 0}, // FunctionParameter
	{0, 0, 0, 0}, // FunctionEnd
	{1, 1, 9, 0}, // FunctionCall
	{1, 1, 0, 0}, // #58
	{1, 1, 0, 1}, // Variable
	{1, 1, 0, 0}, // ImageTexelPointer
	{1, 1, 1, 1}, // Load
	{0, 0, 2, 1}, // Store
	{0, 0, 0, 0}, // CopyMemory
	{0, 0, 0, 0}, // CopyMemorySized
	{1, 1, 0, 1}, // AccessChain
	{1, 1, 0, 0}, // InBoundsAccessChain
	{1, 1, 0, 0}, // PtrAccessChain
	{1, 1, 0, 0}, // ArrayLength
	{1, 1, 0, 0}, // GenericPtrMemSemantics
	{1, 1, 0, 0}, // InBoundsPtrAccessChain
	{0, 0, 0, 1}, // Decorate
	{0, 0, 0, 1}, // MemberDecorate
	{1, 0, 0, 0}, // DecorationGroup
	{0, 0, 0, 0}, // GroupDecorate
	{0, 0, 0, 0}, // GroupMemberDecorate
	{1, 1, 0, 0}, // #76
	{1, 1, 1, 1}, // VectorExtractDynamic
	{1, 1, 2, 1}, // VectorInsertDynamic
	{1, 1, 2, 1}, // VectorShuffle
	{1, 1, 9, 0}, // CompositeConstruct
	{1, 1, 1, 1}, // CompositeExtract
	{1, 1, 2, 1}, // CompositeInsert
	{1, 1, 1, 0}, // CopyObject
	{1, 1, 0, 0}, // Transpose
	{1, 1, 0, 0}, // #85
	{1, 1, 0, 0}, // SampledImage
	{1, 1, 2, 1}, // ImageSampleImplicitLod
	{1, 1, 2, 1}, // ImageSampleExplicitLod
	{1, 1, 3, 1}, // ImageSampleDrefImplicitLod
	{1, 1, 3, 1}, // ImageSampleDrefExplicitLod
	{1, 1, 2, 1}, // ImageSampleProjImplicitLod
	{1, 1, 2, 1}, // ImageSampleProjExplicitLod
	{1, 1, 3, 1}, // ImageSampleProjDrefImplicitLod
	{1, 1, 3, 1}, // ImageSampleProjDrefExplicitLod
	{1, 1, 2, 1}, // ImageFetch
	{1, 1, 3, 1}, // ImageGather
	{1, 1, 3, 1}, // ImageDrefGather
	{1, 1, 2, 1}, // ImageRead
	{0, 0, 3, 1}, // ImageWrite
	{1, 1, 1, 0}, // Image
	{1, 1, 1, 0}, // ImageQueryFormat
	{1, 1, 1, 0}, // ImageQueryOrder
	{1, 1, 2, 0}, // ImageQuerySizeLod
	{1, 1, 1, 0}, // ImageQuerySize
	{1, 1, 2, 0}, // ImageQueryLod
	{1, 1, 1, 0}, // ImageQueryLevels
	{1, 1, 1, 0}, // ImageQuerySamples
	{1, 1, 0, 0}, // #108
	{1, 1, 1, 0}, // ConvertFToU
	{1, 1, 1, 0}, // ConvertFToS
	{1, 1, 1, 0}, // ConvertSToF
	{1, 1, 1, 0}, // ConvertUToF
	{1, 1, 1, 0}, // UConvert
	{1, 1, 1, 0}, // SConvert
	{1, 1, 1, 0}, // FConvert
	{1, 1, 1, 0}, // QuantizeToF16
	{1, 1, 1, 0}, // ConvertPtrToU
	{1, 1, 1, 0}, // SatConvertSToU
	{1, 1, 1, 0}, // SatConvertUToS
	{1, 1, 1, 0}, // ConvertUToPtr
	{1, 1, 1, 0}, // PtrCastToGeneric
	{1, 1, 1, 0}, // GenericCastToPtr
	{1, 1, 1, 1}, // GenericCastToPtrExplicit
	{1, 1, 1, 0}, // Bitcast
	{1, 1, 0, 0}, // #125
	{1, 1, 1, 0}, // SNegate
	{1, 1, 1, 0}, // FNegate
	{1, 1, 2, 0}, // IAdd
	{1, 1, 2, 0}, // FAdd
	{1, 1, 2, 0}, // ISub
	{1, 1, 2, 0}, // FSub
	{1, 1, 2, 0}, // IMul
	{1, 1, 2, 0}, // FMul
	{1, 1, 2, 0}, // UDiv
	{1, 1, 2, 0}, // SDiv
	{1, 1, 2, 0}, // FDiv
	{1, 1, 2, 0}, // UMod
	{1, 1, 2, 0}, // SRem
	{1, 1, 2, 0}, // SMod
	{1, 1, 2, 0}, // FRem
	{1, 1, 2, 0}, // FMod
	{1, 1, 2, 0}, // VectorTimesScalar
	{1, 1, 2, 0}, // MatrixTimesScalar
	{1, 1, 2, 0}, // VectorTimesMatrix
	{1, 1, 2, 0}, // MatrixTimesVector
	{1, 1, 2, 0}, // MatrixTimesMatrix
	{1, 1, 2, 0}, // OuterProduct
	{1, 1, 2, 0}, // Dot
	{1, 1, 2, 0}, // IAddCarry
	{1, 1, 2, 0}, // ISubBorrow
	{1, 1, 2, 0}, // UMulExtended
	{1, 1, 2, 0}, // SMulExtended
	{1, 1, 0, 0}, // #153
	{1, 1, 1, 0}, // Any
	{1, 1, 1, 0}, // All
	{1, 1, 1, 0}, // IsNan
	{1, 1, 1, 0}, // IsInf
	{1, 1, 1, 0}, // IsFinite
	{1, 1, 1, 0}, // IsNormal
	{1, 1, 1, 0}, // SignBitSet
	{1, 1, 2, 0}, // LessOrGreater
	{1, 1, 2, 0}, // Ordered
	{1, 1, 2, 0}, // Unordered
	{1, 1, 2, 0}, // LogicalEqual
	{1, 1, 2, 0}, // LogicalNotEqual
	{1, 1, 2, 0}, // LogicalOr
	{1, 1, 2, 0}, // LogicalAnd
	{1, 1, 1, 0}, // LogicalNot
	{1, 1, 3, 0}, // Select
	{1, 1, 2, 0}, // IEqual
	{1, 1, 2, 0}, // INotEqual
	{1, 1, 2, 0}, // UGreaterThan
	{1, 1, 2, 0}, // SGreaterThan
	{1, 1, 2, 0}, // UGreaterThanEqual
	{1, 1, 2, 0}, // SGreaterThanEqual
	{1, 1, 2, 0}, // ULessThan
	{1, 1, 2, 0}, // SLessThan
	{1, 1, 2, 0}, // ULessThanEqual
	{1, 1, 2, 0}, // SLessThanEqual
	{1, 1, 2, 0}, // FOrdEqual
	{1, 1, 2, 0}, // FUnordEqual
	{1, 1, 2, 0}, // FOrdNotEqual
	{1, 1, 2, 0}, // FUnordNotEqual
	{1, 1, 2, 0}, // FOrdLessThan
	{1, 1, 2, 0}, // FUnordLessThan
	{1, 1, 2, 0}, // FOrdGreaterThan
	{1, 1, 2, 0}, // FUnordGreaterThan
	{1, 1, 2, 0}, // FOrdLessThanEqual
	{1, 1, 2, 0}, // FUnordLessThanEqual
	{1, 1, 2, 0}, // FOrdGreaterThanEqual
	{1, 1, 2, 0}, // FUnordGreaterThanEqual
	{1, 1, 0, 0}, // #192
	{1, 1, 0, 0}, // #193
	{1, 1, 2, 0}, // ShiftRightLogical
	{1, 1, 2, 0}, // ShiftRightArithmetic
	{1, 1, 2, 0}, // ShiftLeftLogical
	{1, 1, 2, 0}, // BitwiseOr
	{1, 1, 2, 0}, // BitwiseXor
	{1, 1, 2, 0}, // BitwiseAnd
	{1, 1, 1, 0}, // Not
	{1, 1, 4, 0}, // BitFieldInsert
	{1, 1, 3, 0}, // BitFieldSExtract
	{1, 1, 3, 0}, // BitFieldUExtract
	{1, 1, 1, 0}, // BitReverse
	{1, 1, 1, 0}, // BitCount
	{1, 1, 0, 0}, // #206
	{1, 1, 0, 0}, // DPdx
	{1, 1, 0, 0}, // DPdy
	{1, 1, 0, 0}, // Fwidth
	{1, 1, 0, 0}, // DPdxFine
	{1, 1, 0, 0}, // DPdyFine
	{1, 1, 0, 0}, // FwidthFine
	{1, 1, 0, 0}, // DPdxCoarse
	{1, 1, 0, 0}, // DPdyCoarse
	{1, 1, 0, 0}, // FwidthCoarse
	{1, 1, 0, 0}, // #216
	{1, 1, 0, 0}, // #217
	{0, 0, 0, 0}, // EmitVertex
	{0, 0, 0, 0}, // EndPrimitive
	{0, 0, 0, 0}, // EmitStreamVertex
	{0, 0, 0, 0}, // EndStreamPrimitive
	{1, 1, 0, 0}, // #222
	{1, 1, 0, 0}, // #223
	{0, 0, 3, 0}, // ControlBarrier
	{0, 0, 2, 0}, // MemoryBarrier
	{1, 1, 0, 0}, // #226
	{1, 1, 0, 0}, // AtomicLoad
	{0, 0, 0, 0}, // AtomicStore
	{1, 1, 0, 0}, // AtomicExchange
	{1, 1, 0, 0}, // AtomicCompareExchange
	{1, 1, 0, 0}, // AtomicCompareExchangeWeak
	{1, 1, 0, 0}, // AtomicIIncrement
	{1, 1, 0, 0}, // AtomicIDecrement
	{1, 1, 0, 0}, // AtomicIAdd
	{1, 1, 0, 0}, // AtomicISub
	{1, 1, 0, 0}, // AtomicSMin
	{1, 1, 0, 0}, // AtomicUMin
	{1, 1, 0, 0}, // AtomicSMax
	{1, 1, 0, 0}, // AtomicUMax
	{1, 1, 0, 0}, // AtomicAnd
	{1, 1, 0, 0}, // AtomicOr
	{1, 1, 0, 0}, // AtomicXor
	{1, 1, 0, 0}, // #243
	{1, 1, 0, 0}, // #244
	{1, 1, 0, 0}, // Phi
	{0, 0, 2, 1}, // LoopMerge
	{0, 0, 1, 1}, // SelectionMerge
	{1, 0, 0, 0}, // Label
	{0, 0, 1, 0}, // Branch
	{0, 0, 3, 1}, // BranchConditional
	{0, 0, 0, 0}, // Switch
	{0, 0, 0, 0}, // Kill
	{0, 0, 0, 0}, // Return
	{0, 0, 0, 0}, // ReturnValue
	{0, 0, 0, 0}, // Unreachable
	{0, 0, 0, 0}, // LifetimeStart
	{0, 0, 0, 0}, // LifetimeStop
	{1, 1, 0, 0}, // #258
	{1, 1, 0, 0}, // GroupAsyncCopy
	{0, 0, 0, 0}, // GroupWaitEvents
	{1, 1, 0, 0}, // GroupAll
	{1, 1, 0, 0}, // GroupAny
	{1, 1, 0, 0}, // GroupBroadcast
	{1, 1, 0, 0}, // GroupIAdd
	{1, 1, 0, 0}, // GroupFAdd
	{1, 1, 0, 0}, // GroupFMin
	{1, 1, 0, 0}, // GroupUMin
	{1, 1, 0, 0}, // GroupSMin
	{1, 1, 0, 0}, // GroupFMax
	{1, 1, 0, 0}, // GroupUMax
	{1, 1, 0, 0}, // GroupSMax
	{1, 1, 0, 0}, // #272
	{1, 1, 0, 0}, // #273
	{1, 1, 0, 0}, // ReadPipe
	{1, 1, 0, 0}, // WritePipe
	{1, 1, 0, 0}, // ReservedReadPipe
	{1, 1, 0, 0}, // ReservedWritePipe
	{1, 1, 0, 0}, // ReserveReadPipePackets
	{1, 1, 0, 0}, // ReserveWritePipePackets
	{0, 0, 0, 0}, // CommitReadPipe
	{0, 0, 0, 0}, // CommitWritePipe
	{1, 1, 0, 0}, // IsValidReserveId
	{1, 1, 0, 0}, // GetNumPipePackets
	{1, 1, 0, 0}, // GetMaxPipePackets
	{1, 1, 0, 0}, // GroupReserveReadPipePackets
	{1, 1, 0, 0}, // GroupReserveWritePipePackets
	{0, 0, 0, 0}, // GroupCommitReadPipe
	{0, 0, 0, 0}, // GroupCommitWritePipe
	{1, 1, 0, 0}, // #289
	{1, 1, 0, 0}, // #290
	{1, 1, 0, 0}, // EnqueueMarker
	{1, 1, 0, 0}, // EnqueueKernel
	{1, 1, 0, 0}, // GetKernelNDrangeSubGroupCount
	{1, 1, 0, 0}, // GetKernelNDrangeMaxSubGroupSize
	{1, 1, 0, 0}, // GetKernelWorkGroupSize
	{1, 1, 0, 0}, // GetKernelPreferredWorkGroupSizeMultiple
	{0, 0, 0, 0}, // RetainEvent
	{0, 0, 0, 0}, // ReleaseEvent
	{1, 1, 0, 0}, // CreateUserEvent
	{1, 1, 0, 0}, // IsValidEvent
	{0, 0, 0, 0}, // SetUserEventStatus
	{0, 0, 0, 0}, // CaptureEventProfilingInfo
	{1, 1, 0, 0}, // GetDefaultQueue
	{1, 1, 0, 0}, // BuildNDRange
	{1, 1, 2, 1}, // ImageSparseSampleImplicitLod
	{1, 1, 2, 1}, // ImageSparseSampleExplicitLod
	{1, 1, 3, 1}, // ImageSparseSampleDrefImplicitLod
	{1, 1, 3, 1}, // ImageSparseSampleDrefExplicitLod
	{1, 1, 2, 1}, // ImageSparseSampleProjImplicitLod
	{1, 1, 2, 1}, // ImageSparseSampleProjExplicitLod
	{1, 1, 3, 1}, // ImageSparseSampleProjDrefImplicitLod
	{1, 1, 3, 1}, // ImageSparseSampleProjDrefExplicitLod
	{1, 1, 2, 1}, // ImageSparseFetch
	{1, 1, 3, 1}, // ImageSparseGather
	{1, 1, 3, 1}, // ImageSparseDrefGather
	{1, 1, 1, 0}, // ImageSparseTexelsResident
	{0, 0, 0, 0}, // NoLine
	{1, 1, 0, 0}, // AtomicFlagTestAndSet
	{0, 0, 0, 0}, // AtomicFlagClear
	{1, 1, 0, 0}, // ImageSparseRead
	{1, 1, 0, 0}, // SizeOf
	{1, 1, 0, 0}, // TypePipeStorage
	{1, 1, 0, 0}, // ConstantPipeStorage
	{1, 1, 0, 0}, // CreatePipeFromPipeStorage
	{1, 1, 0, 0}, // GetKernelLocalSizeForSubgroupCount
	{1, 1, 0, 0}, // GetKernelMaxNumSubgroups
	{1, 1, 0, 0}, // TypeNamedBarrier
	{1, 1, 0, 1}, // NamedBarrierInitialize
	{0, 0, 2, 1}, // MemoryNamedBarrier
	{1, 1, 0, 0}, // ModuleProcessed
	{0, 0, 0, 1}, // ExecutionModeId
	{0, 0, 0, 1}, // DecorateId
	{1, 1, 1, 1}, // GroupNonUniformElect
	{1, 1, 1, 1}, // GroupNonUniformAll
	{1, 1, 1, 1}, // GroupNonUniformAny
	{1, 1, 1, 1}, // GroupNonUniformAllEqual
	{1, 1, 1, 1}, // GroupNonUniformBroadcast
	{1, 1, 1, 1}, // GroupNonUniformBroadcastFirst
	{1, 1, 1, 1}, // GroupNonUniformBallot
	{1, 1, 1, 1}, // GroupNonUniformInverseBallot
	{1, 1, 1, 1}, // GroupNonUniformBallotBitExtract
	{1, 1, 1, 1}, // GroupNonUniformBallotBitCount
	{1, 1, 1, 1}, // GroupNonUniformBallotFindLSB
	{1, 1, 1, 1}, // GroupNonUniformBallotFindMSB
	{1, 1, 1, 1}, // GroupNonUniformShuffle
	{1, 1, 1, 1}, // GroupNonUniformShuffleXor
	{1, 1, 1, 1}, // GroupNonUniformShuffleUp
	{1, 1, 1, 1}, // GroupNonUniformShuffleDown
	{1, 1, 1, 1}, // GroupNonUniformIAdd
	{1, 1, 1, 1}, // GroupNonUniformFAdd
	{1, 1, 1, 1}, // GroupNonUniformIMul
	{1, 1, 1, 1}, // GroupNonUniformFMul
	{1, 1, 1, 1}, // GroupNonUniformSMin
	{1, 1, 1, 1}, // GroupNonUniformUMin
	{1, 1, 1, 1}, // GroupNonUniformFMin
	{1, 1, 1, 1}, // GroupNonUniformSMax
	{1, 1, 1, 1}, // GroupNonUniformUMax
	{1, 1, 1, 1}, // GroupNonUniformFMax
	{1, 1, 1, 1}, // GroupNonUniformBitwiseAnd
	{1, 1, 1, 1}, // GroupNonUniformBitwiseOr
	{1, 1, 1, 1}, // GroupNonUniformBitwiseXor
	{1, 1, 1, 1}, // GroupNonUniformLogicalAnd
	{1, 1, 1, 1}, // GroupNonUniformLogicalOr
	{1, 1, 1, 1}, // GroupNonUniformLogicalXor
	{1, 1, 1, 1}, // GroupNonUniformQuadBroadcast
	{1, 1, 1, 1}, // GroupNonUniformQuadSwap
};
static_assert(_SMOLV_ARRAY_SIZE(kSpirvOpData) == kKnownOpsCount, "kSpirvOpData table mismatch with known SpvOps");

// Instruction encoding depends on the table that describes the various SPIR-V opcodes.
// Whenever we change or expand the table, we need to bump up the SMOL-V version, and make
// sure that we can still decode files encoded by an older version.
static int smolv_GetKnownOpsCount(int version)
{
	if (version == 0)
		return SpvOpModuleProcessed+1;
	if (version == 1) // 2020 February, version 1 added ExecutionModeId..GroupNonUniformQuadSwap
		return SpvOpGroupNonUniformQuadSwap+1;
	return 0;
}

static bool smolv_OpHasResult(SpvOp op, int opsCount)
{
	if (op < 0 || op >= opsCount)
		return false;
	return kSpirvOpData[op].hasResult != 0;
}

static bool smolv_OpHasType(SpvOp op, int opsCount)
{
	if (op < 0 || op >= opsCount)
		return false;
	return kSpirvOpData[op].hasType != 0;
}

static int smolv_OpDeltaFromResult(SpvOp op, int opsCount)
{
	if (op < 0 || op >= opsCount)
		return 0;
	return kSpirvOpData[op].deltaFromResult;
}

static bool smolv_OpVarRest(SpvOp op, int opsCount)
{
	if (op < 0 || op >= opsCount)
		return false;
	return kSpirvOpData[op].varrest != 0;
}

static bool smolv_OpDebugInfo(SpvOp op, int opsCount)
{
	return
		op == SpvOpSourceContinued ||
		op == SpvOpSource ||
		op == SpvOpSourceExtension ||
		op == SpvOpName ||
		op == SpvOpMemberName ||
		op == SpvOpString ||
		op == SpvOpLine ||
		op == SpvOpNoLine ||
		op == SpvOpModuleProcessed;
}


static int smolv_DecorationExtraOps(int dec)
{
	if (dec == 0 || (dec >= 2 && dec <= 5)) // RelaxedPrecision, Block..ColMajor
		return 0;
	if (dec >= 29 && dec <= 37) // Stream..XfbStride
		return 1;
	return -1; // unknown, encode length
}


// --------------------------------------------------------------------------------------------


static bool smolv_CheckGenericHeader(const uint32_t* words, size_t wordCount, uint32_t expectedMagic, uint32_t versionMask)
{
	if (!words)
		return false;
	if (wordCount < 5)
		return false;
	
	uint32_t headerMagic = words[0];
	if (headerMagic != expectedMagic)
		return false;
	uint32_t headerVersion = words[1] & versionMask;
	if (headerVersion < 0x00010000 || headerVersion > 0x00010600)
		return false; // only support 1.0 through 1.6
	
	return true;
}

static const int kSpirVHeaderMagic = 0x07230203;
static const int kSmolHeaderMagic = 0x534D4F4C; // "SMOL"

static const int kSmolCurrEncodingVersion = 1;

static bool smolv_CheckSpirVHeader(const uint32_t* words, size_t wordCount)
{
	//@TODO: if SPIR-V header magic was reversed, that means the file got written
	// in a "big endian" order. Need to byteswap all words then.
	return smolv_CheckGenericHeader(words, wordCount, kSpirVHeaderMagic, 0xFFFFFFFF);
}
static bool smolv_CheckSmolHeader(const uint8_t* bytes, size_t byteCount)
{
	if (!smolv_CheckGenericHeader((const uint32_t*)bytes, byteCount/4, kSmolHeaderMagic, 0x00FFFFFF))
		return false;
	if (byteCount < 24) // one more word past header to store decoded length
		return false;
	// SMOL-V version
	int smolVersion = ((const uint32_t*)bytes)[1] >> 24;
	if (smolVersion < 0 || smolVersion > kSmolCurrEncodingVersion)
		return false;
	return true;
}


static void smolv_Write4(smolv::ByteArray& arr, uint32_t v)
{
	arr.push_back(v & 0xFF);
	arr.push_back((v >> 8) & 0xFF);
	arr.push_back((v >> 16) & 0xFF);
	arr.push_back(v >> 24);
}

static void smolv_Write4(uint8_t*& buf, uint32_t v)
{
	memcpy(buf, &v, 4);
	buf += 4;
}


static bool smolv_Read4(const uint8_t*& data, const uint8_t* dataEnd, uint32_t& outv)
{
	if (data + 4 > dataEnd)
		return false;
	outv = (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
	data += 4;
	return true;
}


// --------------------------------------------------------------------------------------------

// Variable-length integer encoding for unsigned integers. In each byte:
// - highest bit set if more bytes follow, cleared if this is last byte.
// - other 7 bits are the actual value payload.
// Takes 1-5 bytes to encode an integer (values between 0 and 127 take one byte, etc.).

static void smolv_WriteVarint(smolv::ByteArray& arr, uint32_t v)
{
	while (v > 127)
	{
		arr.push_back((v & 127) | 128);
		v >>= 7;
	}
	arr.push_back(v & 127);
}

static bool smolv_ReadVarint(const uint8_t*& data, const uint8_t* dataEnd, uint32_t& outVal)
{
	uint32_t v = 0;
	uint32_t shift = 0;
	while (data < dataEnd)
	{
		uint8_t b = *data;
		v |= (b & 127) << shift;
		shift += 7;
		data++;
		if (!(b & 128))
			break;
	}
	outVal = v;
	return true; //@TODO: report failures
}

static uint32_t smolv_ZigEncode(int32_t i)
{
	return (uint32_t(i) << 1) ^ (i >> 31);
}

static int32_t smolv_ZigDecode(uint32_t u)
{
	 return (u & 1) ? ((u >> 1) ^ ~0) : (u >> 1);
}


// Remap most common Op codes (Load, Store, Decorate, VectorShuffle etc.) to be in < 16 range, for
// more compact varint encoding. This basically swaps rarely used op values that are < 16 with the
// ones that are common.

static SpvOp smolv_RemapOp(SpvOp op)
{
#	define _SMOLV_SWAP_OP(op1,op2) if (op==op1) return op2; if (op==op2) return op1
	_SMOLV_SWAP_OP(SpvOpDecorate,SpvOpNop); // 0: 24%
	_SMOLV_SWAP_OP(SpvOpLoad,SpvOpUndef); // 1: 17%
	_SMOLV_SWAP_OP(SpvOpStore,SpvOpSourceContinued); // 2: 9%
	_SMOLV_SWAP_OP(SpvOpAccessChain,SpvOpSource); // 3: 7.2%
	_SMOLV_SWAP_OP(SpvOpVectorShuffle,SpvOpSourceExtension); // 4: 5.0%
	// Name - already small enum value - 5: 4.4%
	// MemberName - already small enum value - 6: 2.9%
	_SMOLV_SWAP_OP(SpvOpMemberDecorate,SpvOpString); // 7: 4.0%
	_SMOLV_SWAP_OP(SpvOpLabel,SpvOpLine); // 8: 0.9%
	_SMOLV_SWAP_OP(SpvOpVariable,(SpvOp)9); // 9: 3.9%
	_SMOLV_SWAP_OP(SpvOpFMul,SpvOpExtension); // 10: 3.9%
	_SMOLV_SWAP_OP(SpvOpFAdd,SpvOpExtInstImport); // 11: 2.5%
	// ExtInst - already small enum value - 12: 1.2%
	// VectorShuffleCompact - already small enum value - used for compact shuffle encoding
	_SMOLV_SWAP_OP(SpvOpTypePointer,SpvOpMemoryModel); // 14: 2.2%
	_SMOLV_SWAP_OP(SpvOpFNegate,SpvOpEntryPoint); // 15: 1.1%
#	undef _SMOLV_SWAP_OP
	return op;
}


// For most compact varint encoding of common instructions, the instruction length should come out
// into 3 bits (be <8). SPIR-V instruction lengths are always at least 1, and for some other
// instructions they are guaranteed to be some other minimum length. Adjust the length before encoding,
// and after decoding accordingly.

static uint32_t smolv_EncodeLen(SpvOp op, uint32_t len)
{
	len--;
	if (op == SpvOpVectorShuffle)			len -= 4;
	if (op == SpvOpVectorShuffleCompact)	len -= 4;
	if (op == SpvOpDecorate)				len -= 2;
	if (op == SpvOpLoad)					len -= 3;
	if (op == SpvOpAccessChain)				len -= 3;
	return len;
}

static uint32_t smolv_DecodeLen(SpvOp op, uint32_t len)
{
	len++;
	if (op == SpvOpVectorShuffle)			len += 4;
	if (op == SpvOpVectorShuffleCompact)	len += 4;
	if (op == SpvOpDecorate)				len += 2;
	if (op == SpvOpLoad)					len += 3;
	if (op == SpvOpAccessChain)				len += 3;
	return len;
}


// Shuffling bits of length + opcode to be more compact in varint encoding in typical cases:
// 0x LLLL OOOO is how SPIR-V encodes it (L=length, O=op), we shuffle into:
// 0x LLLO OOLO, so that common case (op<16, len<8) is encoded into one byte.

static bool smolv_WriteLengthOp(smolv::ByteArray& arr, uint32_t len, SpvOp op)
{
	len = smolv_EncodeLen(op, len);
	// SPIR-V length field is 16 bits; if we get a larger value that means something
	// was wrong, e.g. a vector shuffle instruction with less than 4 words (and our
	// adjustment to common lengths in smolv_EncodeLen wrapped around)
	if (len > 0xFFFF)
		return false;
	op = smolv_RemapOp(op);
	uint32_t oplen = ((len >> 4) << 20) | ((op >> 4) << 8) | ((len & 0xF) << 4) | (op & 0xF);
	smolv_WriteVarint(arr, oplen);
	return true;
}

static bool smolv_ReadLengthOp(const uint8_t*& data, const uint8_t* dataEnd, uint32_t& outLen, SpvOp& outOp)
{
	uint32_t val;
	if (!smolv_ReadVarint(data, dataEnd, val))
		return false;
	outLen = ((val >> 20) << 4) | ((val >> 4) & 0xF);
	outOp = (SpvOp)(((val >> 4) & 0xFFF0) | (val & 0xF));

	outOp = smolv_RemapOp(outOp);
	outLen = smolv_DecodeLen(outOp, outLen);
	return true;
}



#define _SMOLV_READ_OP(len, words, op) \
	uint32_t len = words[0] >> 16; \
	if (len < 1) return false; /* malformed instruction, length needs to be at least 1 */ \
	if (words + len > wordsEnd) return false; /* malformed instruction, goes past end of data */ \
	SpvOp op = (SpvOp)(words[0] & 0xFFFF)


bool smolv::Encode(const void* spirvData, size_t spirvSize, ByteArray& outSmolv, uint32_t flags, StripOpNameFilterFunc stripFilter)
{
	const size_t wordCount = spirvSize / 4;
	if (wordCount * 4 != spirvSize)
		return false;
	const uint32_t* words = (const uint32_t*)spirvData;
	const uint32_t* wordsEnd = words + wordCount;
	if (!smolv_CheckSpirVHeader(words, wordCount))
		return false;

	// reserve space in output (typical compression is to about 30%; reserve half of input space)
	outSmolv.reserve(outSmolv.size() + spirvSize/2);

	// header (matches SPIR-V one, except different magic)
	smolv_Write4(outSmolv, kSmolHeaderMagic);
	smolv_Write4(outSmolv, (words[1] & 0x00FFFFFF) + (kSmolCurrEncodingVersion<<24)); // SPIR-V version (_XXX) + SMOL-V version (X___)
	smolv_Write4(outSmolv, words[2]); // generator
	smolv_Write4(outSmolv, words[3]); // bound
	smolv_Write4(outSmolv, words[4]); // schema

	const size_t headerSpirvSizeOffset = outSmolv.size(); // size field may get updated later if stripping is enabled
	smolv_Write4(outSmolv, (uint32_t)spirvSize); // space needed to decode (i.e. original SPIR-V size)

	size_t strippedSpirvWordCount = wordCount;
	uint32_t prevResult = 0;
	uint32_t prevDecorate = 0;
	
	const int knownOpsCount = smolv_GetKnownOpsCount(kSmolCurrEncodingVersion);

	words += 5;
	while (words < wordsEnd)
	{
		_SMOLV_READ_OP(instrLen, words, op);

		if ((flags & kEncodeFlagStripDebugInfo) && smolv_OpDebugInfo(op, knownOpsCount))
		{
			if (!stripFilter || op != SpvOpName || !stripFilter(reinterpret_cast<const char*>(&words[2])))
			{
				strippedSpirvWordCount -= instrLen;
				words += instrLen;
				continue;
			}
		}

		// A usual case of vector shuffle, with less than 4 components, each with a value
		// in [0..3] range: encode it in a more compact form, with the swizzle pattern in one byte.
		// Turn this into a VectorShuffleCompact instruction, that takes up unused slot in Ops.
		uint32_t swizzle = 0;
		if (op == SpvOpVectorShuffle && instrLen <= 9)
		{
			uint32_t swz0 = instrLen > 5 ? words[5] : 0;
			uint32_t swz1 = instrLen > 6 ? words[6] : 0;
			uint32_t swz2 = instrLen > 7 ? words[7] : 0;
			uint32_t swz3 = instrLen > 8 ? words[8] : 0;
			if (swz0 < 4 && swz1 < 4 && swz2 < 4 && swz3 < 4)
			{
				op = SpvOpVectorShuffleCompact;
				swizzle = (swz0 << 6) | (swz1 << 4) | (swz2 << 2) | (swz3);
			}
		}

		// length + opcode
		if (!smolv_WriteLengthOp(outSmolv, instrLen, op))
			return false;

		size_t ioffs = 1;
		// write type as varint, if we have it
		if (smolv_OpHasType(op, knownOpsCount))
		{
			if (ioffs >= instrLen)
				return false;
			smolv_WriteVarint(outSmolv, words[ioffs]);
			ioffs++;
		}
		// write result as delta+zig+varint, if we have it
		if (smolv_OpHasResult(op, knownOpsCount))
		{
			if (ioffs >= instrLen)
				return false;
			uint32_t v = words[ioffs];
			smolv_WriteVarint(outSmolv, smolv_ZigEncode(v - prevResult)); // some deltas are negative, use zig
			prevResult = v;
			ioffs++;
		}

		// Decorate & MemberDecorate: IDs relative to previous decorate
		if (op == SpvOpDecorate || op == SpvOpMemberDecorate)
		{
			if (ioffs >= instrLen)
				return false;
			uint32_t v = words[ioffs];
			smolv_WriteVarint(outSmolv, smolv_ZigEncode(v - prevDecorate)); // spirv-remapped deltas often negative, use zig
			prevDecorate = v;
			ioffs++;
		}

		// MemberDecorate special encoding: whole row of MemberDecorate instructions is often referring
		// to the same type and linearly increasing member indices. Scan ahead to see how many we have,
		// and encode whole bunch as one.
		if (op == SpvOpMemberDecorate)
		{
			// scan ahead until we reach end, non-member-decoration or different type
			const uint32_t decorationType = words[ioffs-1];
			const uint32_t* memberWords = words;
			uint32_t prevIndex = 0;
			uint32_t prevOffset = 0;
			// write a byte on how many we have encoded as a bunch
			size_t countLocation = outSmolv.size();
			outSmolv.push_back(0);
			int count = 0;
			while (memberWords < wordsEnd && count < 255)
			{
				_SMOLV_READ_OP(memberLen, memberWords, memberOp);
				if (memberOp != SpvOpMemberDecorate)
					break;
				if (memberLen < 4)
					return false; // invalid input
				if (memberWords[1] != decorationType)
					break;

				// write member index as delta from previous
				uint32_t memberIndex = memberWords[2];
				smolv_WriteVarint(outSmolv, memberIndex - prevIndex);
				prevIndex = memberIndex;

				// decoration (and length if not common/known)
				uint32_t memberDec = memberWords[3];
				smolv_WriteVarint(outSmolv, memberDec);
				const int knownExtraOps = smolv_DecorationExtraOps(memberDec);
				if (knownExtraOps == -1)
					smolv_WriteVarint(outSmolv, memberLen-4);
				else if (unsigned(knownExtraOps) + 4 != memberLen)
					return false; // invalid input

				// Offset decorations are most often linearly increasing, so encode as deltas
				if (memberDec == 35) // Offset
				{
					if (memberLen != 5)
						return false;
					smolv_WriteVarint(outSmolv, memberWords[4]-prevOffset);
					prevOffset = memberWords[4];
				}
				else
				{
					// write rest of decorations as varint
					for (uint32_t i = 4; i < memberLen; ++i)
						smolv_WriteVarint(outSmolv, memberWords[i]);
				}

				memberWords += memberLen;
				++count;
			}
			outSmolv[countLocation] = uint8_t(count);
			words = memberWords;
			continue;
		}

		// Write out this many IDs, encoding them relative+zigzag to result ID
		int relativeCount = smolv_OpDeltaFromResult(op, knownOpsCount);
		for (int i = 0; i < relativeCount && ioffs < instrLen; ++i, ++ioffs)
		{
			if (ioffs >= instrLen)
				return false;
			uint32_t delta = prevResult - words[ioffs];
			// some deltas are negative (often on branches, or if program was processed by spirv-remap),
			// so use zig encoding
			smolv_WriteVarint(outSmolv, smolv_ZigEncode(delta));
		}

		if (op == SpvOpVectorShuffleCompact)
		{
			// compact vector shuffle, just write out single swizzle byte
			outSmolv.push_back(uint8_t(swizzle));
			ioffs = instrLen;
		}
		else if (smolv_OpVarRest(op, knownOpsCount))
		{
			// write out rest of words with variable encoding (expected to be small integers)
			for (; ioffs < instrLen; ++ioffs)
				smolv_WriteVarint(outSmolv, words[ioffs]);
		}
		else
		{
			// write out rest of words without any encoding
			for (; ioffs < instrLen; ++ioffs)
				smolv_Write4(outSmolv, words[ioffs]);
		}
		
		words += instrLen;
	}

	if (strippedSpirvWordCount != wordCount)
	{
		uint8_t* headerSpirvSize = &outSmolv[headerSpirvSizeOffset];
		smolv_Write4(headerSpirvSize, (uint32_t)strippedSpirvWordCount * 4);
	}
	
	return true;
}


size_t smolv::GetDecodedBufferSize(const void* smolvData, size_t smolvSize)
{
	if (!smolv_CheckSmolHeader((const uint8_t*)smolvData, smolvSize))
		return 0;
	const uint32_t* words = (const uint32_t*)smolvData;
	return words[5];
}


bool smolv::Decode(const void* smolvData, size_t smolvSize, void* spirvOutputBuffer, size_t spirvOutputBufferSize, uint32_t flags)
{
	// check header, and whether we have enough output buffer space
	const size_t neededBufferSize = GetDecodedBufferSize(smolvData, smolvSize);
	if (neededBufferSize == 0)
		return false; // invalid SMOL-V
	if (spirvOutputBufferSize < neededBufferSize)
		return false; // not enough space in output buffer
	if (spirvOutputBuffer == NULL)
		return false; // output buffer is null

	const uint8_t* bytes = (const uint8_t*)smolvData;
	const uint8_t* bytesEnd = bytes + smolvSize;

	uint8_t* outSpirv = (uint8_t*)spirvOutputBuffer;
	
	uint32_t val;
	int smolVersion = 0;

	// header
	smolv_Write4(outSpirv, kSpirVHeaderMagic); bytes += 4;
	smolv_Read4(bytes, bytesEnd, val); smolVersion = val >> 24; val &= 0x00FFFFFF; smolv_Write4(outSpirv, val); // version
	smolv_Read4(bytes, bytesEnd, val); smolv_Write4(outSpirv, val); // generator
	smolv_Read4(bytes, bytesEnd, val); smolv_Write4(outSpirv, val); // bound
	smolv_Read4(bytes, bytesEnd, val); smolv_Write4(outSpirv, val); // schema
	bytes += 4; // decode buffer size
	
	// there are two SMOL-V encoding versions, both not indicating anything in their header version field:
	// one that is called "before zero" here (2016-08-31 code). Support decoding that one only by presence
	// of this special flag.
	const bool beforeZeroVersion = smolVersion == 0 && (flags & kDecodeFlagUse20160831AsZeroVersion) != 0;

	const int knownOpsCount = smolv_GetKnownOpsCount(smolVersion);

	uint32_t prevResult = 0;
	uint32_t prevDecorate = 0;

	while (bytes < bytesEnd)
	{
		// read length + opcode
		uint32_t instrLen;
		SpvOp op;
		if (!smolv_ReadLengthOp(bytes, bytesEnd, instrLen, op))
			return false;
		const bool wasSwizzle = (op == SpvOpVectorShuffleCompact);
		if (wasSwizzle)
			op = SpvOpVectorShuffle;
		smolv_Write4(outSpirv, (instrLen << 16) | op);

		size_t ioffs = 1;

		// read type as varint, if we have it
		if (smolv_OpHasType(op, knownOpsCount))
		{
			if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
			smolv_Write4(outSpirv, val);
			ioffs++;
		}
		// read result as delta+varint, if we have it
		if (smolv_OpHasResult(op, knownOpsCount))
		{
			if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
			val = prevResult + smolv_ZigDecode(val);
			smolv_Write4(outSpirv, val);
			prevResult = val;
			ioffs++;
		}
		
		// Decorate: IDs relative to previous decorate
		if (op == SpvOpDecorate || op == SpvOpMemberDecorate)
		{
			if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
			// "before zero" version did not use zig encoding for the value
			val = prevDecorate + (beforeZeroVersion ? val : smolv_ZigDecode(val));
			smolv_Write4(outSpirv, val);
			prevDecorate = val;
			ioffs++;
		}

		// MemberDecorate special decoding
		if (op == SpvOpMemberDecorate && !beforeZeroVersion)
		{
			if (bytes >= bytesEnd)
				return false; // broken input
			int count = *bytes++;
			int prevIndex = 0;
			int prevOffset = 0;
			for (int m = 0; m < count; ++m)
			{
				// read member index
				uint32_t memberIndex;
				if (!smolv_ReadVarint(bytes, bytesEnd, memberIndex)) return false;
				memberIndex += prevIndex;
				prevIndex = memberIndex;
				
				// decoration (and length if not common/known)
				uint32_t memberDec;
				if (!smolv_ReadVarint(bytes, bytesEnd, memberDec)) return false;
				const int knownExtraOps = smolv_DecorationExtraOps(memberDec);
				uint32_t memberLen;
				if (knownExtraOps == -1)
				{
					if (!smolv_ReadVarint(bytes, bytesEnd, memberLen)) return false;
					memberLen += 4;
				}
				else
					memberLen = 4 + knownExtraOps;

				// write SPIR-V op+length (unless it's first member decoration, in which case it was written before)
				if (m != 0)
				{
					smolv_Write4(outSpirv, (memberLen << 16) | op);
					smolv_Write4(outSpirv, prevDecorate);
				}
				smolv_Write4(outSpirv, memberIndex);
				smolv_Write4(outSpirv, memberDec);
				// Special case for Offset decorations
				if (memberDec == 35) // Offset
				{
					if (memberLen != 5)
						return false;
					if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
					val += prevOffset;
					smolv_Write4(outSpirv, val);
					prevOffset = val;
				}
				else
				{
					for (uint32_t i = 4; i < memberLen; ++i)
					{
						if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
						smolv_Write4(outSpirv, val);
					}
				}
			}
			continue;
		}

		// Read this many IDs, that are relative to result ID
		int relativeCount = smolv_OpDeltaFromResult(op, knownOpsCount);
		// "before zero" version only used zig encoding for IDs of several ops; after
		// that ops got zig encoding for their IDs
		bool zigDecodeVals = true;
		if (beforeZeroVersion)
		{
			if (op != SpvOpControlBarrier && op != SpvOpMemoryBarrier && op != SpvOpLoopMerge && op != SpvOpSelectionMerge && op != SpvOpBranch && op != SpvOpBranchConditional && op != SpvOpMemoryNamedBarrier)
				zigDecodeVals = false;
		}
		for (int i = 0; i < relativeCount && ioffs < instrLen; ++i, ++ioffs)
		{
			if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
			if (zigDecodeVals)
				val = smolv_ZigDecode(val);
			smolv_Write4(outSpirv, prevResult - val);
		}

		if (wasSwizzle && instrLen <= 9)
		{
			uint32_t swizzle = *bytes++;
			if (instrLen > 5) smolv_Write4(outSpirv, (swizzle >> 6) & 3);
			if (instrLen > 6) smolv_Write4(outSpirv, (swizzle >> 4) & 3);
			if (instrLen > 7) smolv_Write4(outSpirv, (swizzle >> 2) & 3);
			if (instrLen > 8) smolv_Write4(outSpirv, swizzle & 3);
		}
		else if (smolv_OpVarRest(op, knownOpsCount))
		{
			// read rest of words with variable encoding
			for (; ioffs < instrLen; ++ioffs)
			{
				if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
				smolv_Write4(outSpirv, val);
			}
		}
		else
		{
			// read rest of words without any encoding
			for (; ioffs < instrLen; ++ioffs)
			{
				if (!smolv_Read4(bytes, bytesEnd, val)) return false;
				smolv_Write4(outSpirv, val);
			}
		}
	}

	if ((uint8_t*)spirvOutputBuffer + neededBufferSize != outSpirv)
		return false; // something went wrong during decoding? we should have decoded to exact output size
	
	return true;
}



// --------------------------------------------------------------------------------------------
// Calculating instruction count / space stats on SPIR-V and SMOL-V


struct smolv::Stats
{
	Stats() { memset(this, 0, sizeof(*this)); }
	size_t opCounts[kKnownOpsCount];
	size_t opSizes[kKnownOpsCount];
	size_t smolOpSizes[kKnownOpsCount];
	size_t varintCountsOp[6];
	size_t varintCountsType[6];
	size_t varintCountsRes[6];
	size_t varintCountsOther[6];
	size_t totalOps;
	size_t totalSize;
	size_t totalSizeSmol;
	size_t inputCount;
};


smolv::Stats* smolv::StatsCreate()
{
	return new Stats();
}

void smolv::StatsDelete(smolv::Stats *s)
{
	delete s;
}


bool smolv::StatsCalculate(smolv::Stats* stats, const void* spirvData, size_t spirvSize)
{
	if (!stats)
		return false;

	const size_t wordCount = spirvSize / 4;
	if (wordCount * 4 != spirvSize)
		return false;
	const uint32_t* words = (const uint32_t*)spirvData;
	const uint32_t* wordsEnd = words + wordCount;
	if (!smolv_CheckSpirVHeader(words, wordCount))
		return false;
	words += 5;
	
	stats->inputCount++;
	stats->totalSize += wordCount;

	while (words < wordsEnd)
	{
		_SMOLV_READ_OP(instrLen, words, op);

		if (op < kKnownOpsCount)
		{
			stats->opCounts[op]++;
			stats->opSizes[op] += instrLen;
		}
		words += instrLen;
		stats->totalOps++;
	}
	
	return true;
}


bool smolv::StatsCalculateSmol(smolv::Stats* stats, const void* smolvData, size_t smolvSize)
{
	if (!stats)
		return false;

	// debugging helper to dump all encoded bytes to stdout, keep at "if 0"
#	if 0
#		define _SMOLV_DEBUG_PRINT_ENCODED_BYTES() { \
			printf("Op %-22s ", op < kKnownOpsCount ? kSpirvOpNames[op] : "???"); \
			for (const uint8_t* b = instrBegin; b < bytes; ++b) \
				printf("%02x ", *b); \
			printf("\n"); \
		}
#	else
#		define _SMOLV_DEBUG_PRINT_ENCODED_BYTES() {}
#	endif
	
	const uint8_t* bytes = (const uint8_t*)smolvData;
	const uint8_t* bytesEnd = bytes + smolvSize;
	if (!smolv_CheckSmolHeader(bytes, smolvSize))
		return false;

	uint32_t val;
	int smolVersion;
	bytes += 4;
	smolv_Read4(bytes, bytesEnd, val); smolVersion = val >> 24;
	const int knownOpsCount = smolv_GetKnownOpsCount(smolVersion);
	bytes += 16;
	
	stats->totalSizeSmol += smolvSize;
	
	while (bytes < bytesEnd)
	{
		const uint8_t* instrBegin = bytes;
		const uint8_t* varBegin;

		// read length + opcode
		uint32_t instrLen;
		SpvOp op;
		varBegin = bytes;
		if (!smolv_ReadLengthOp(bytes, bytesEnd, instrLen, op))
			return false;
		const bool wasSwizzle = (op == SpvOpVectorShuffleCompact);
		if (wasSwizzle)
			op = SpvOpVectorShuffle;
		stats->varintCountsOp[bytes-varBegin]++;
		
		size_t ioffs = 1;
		if (smolv_OpHasType(op, knownOpsCount))
		{
			varBegin = bytes;
			if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
			stats->varintCountsType[bytes-varBegin]++;
			ioffs++;
		}
		if (smolv_OpHasResult(op, knownOpsCount))
		{
			varBegin = bytes;
			if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
			stats->varintCountsRes[bytes-varBegin]++;
			ioffs++;
		}
		
		if (op == SpvOpDecorate || op == SpvOpMemberDecorate)
		{
			if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
			ioffs++;
		}
		// MemberDecorate special decoding
		if (op == SpvOpMemberDecorate)
		{
			if (bytes >= bytesEnd)
				return false; // broken input
			int count = *bytes++;
			for (int m = 0; m < count; ++m)
			{
				uint32_t memberIndex;
				if (!smolv_ReadVarint(bytes, bytesEnd, memberIndex)) return false;
				uint32_t memberDec;
				if (!smolv_ReadVarint(bytes, bytesEnd, memberDec)) return false;
				const int knownExtraOps = smolv_DecorationExtraOps(memberDec);
				uint32_t memberLen;
				if (knownExtraOps == -1)
				{
					if (!smolv_ReadVarint(bytes, bytesEnd, memberLen)) return false;
					memberLen += 4;
				}
				else
					memberLen = 4 + knownExtraOps;
				for (uint32_t i = 4; i < memberLen; ++i)
				{
					if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
				}
			}
			stats->smolOpSizes[op] += bytes - instrBegin;
			_SMOLV_DEBUG_PRINT_ENCODED_BYTES();
			continue;
		}

		int relativeCount = smolv_OpDeltaFromResult(op, knownOpsCount);
		for (int i = 0; i < relativeCount && ioffs < instrLen; ++i, ++ioffs)
		{
			varBegin = bytes;
			if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
			stats->varintCountsRes[bytes-varBegin]++;
		}

		if (wasSwizzle && instrLen <= 9)
		{
			bytes++;
		}
		else if (smolv_OpVarRest(op, knownOpsCount))
		{
			for (; ioffs < instrLen; ++ioffs)
			{
				varBegin = bytes;
				if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
				stats->varintCountsOther[bytes-varBegin]++;
			}
		}
		else
		{
			for (; ioffs < instrLen; ++ioffs)
			{
				if (!smolv_Read4(bytes, bytesEnd, val)) return false;
			}
		}
		
		if (op < kKnownOpsCount)
		{
			stats->smolOpSizes[op] += bytes - instrBegin;
		}
		_SMOLV_DEBUG_PRINT_ENCODED_BYTES();
	}
	
	return true;
}

static bool CompareOpCounters (std::pair<SpvOp,size_t> a, std::pair<SpvOp,size_t> b)
{
	return a.second > b.second;
}

void smolv::StatsPrint(const Stats* stats)
{
	if (!stats)
		return;

	typedef std::pair<SpvOp,size_t> OpCounter;
	OpCounter counts[kKnownOpsCount];
	OpCounter sizes[kKnownOpsCount];
	OpCounter sizesSmol[kKnownOpsCount];
	for (int i = 0; i < kKnownOpsCount; ++i)
	{
		counts[i].first = (SpvOp)i;
		counts[i].second = stats->opCounts[i];
		sizes[i].first = (SpvOp)i;
		sizes[i].second = stats->opSizes[i];
		sizesSmol[i].first = (SpvOp)i;
		sizesSmol[i].second = stats->smolOpSizes[i];
	}
	std::sort(counts, counts + kKnownOpsCount, CompareOpCounters);
	std::sort(sizes, sizes + kKnownOpsCount, CompareOpCounters);
	std::sort(sizesSmol, sizesSmol + kKnownOpsCount, CompareOpCounters);
	
	printf("Stats for %i SPIR-V inputs, total size %i words (%.1fKB):\n", (int)stats->inputCount, (int)stats->totalSize, stats->totalSize * 4.0f / 1024.0f);
	printf("Most occuring ops:\n");
	for (int i = 0; i < 30; ++i)
	{
		SpvOp op = counts[i].first;
		printf(" #%2i: %4i %-20s %4i (%4.1f%%)\n", i, op, kSpirvOpNames[op], (int)counts[i].second, (float)counts[i].second / (float)stats->totalOps * 100.0f);
	}
	printf("Largest total size of ops:\n");
	for (int i = 0; i < 30; ++i)
	{
		SpvOp op = sizes[i].first;
		printf(" #%2i: %-22s %6i (%4.1f%%) avg len %.1f\n",
			   i,
			   kSpirvOpNames[op],
			   (int)sizes[i].second*4,
			   (float)sizes[i].second / (float)stats->totalSize * 100.0f,
			   (float)sizes[i].second*4 / (float)stats->opCounts[op]
		);
	}
	printf("SMOL varint encoding counts per byte length:\n");
	printf("  B: %6s %6s %6s %6s\n", "Op", "Type", "Result", "Other");
	for (int i = 1; i < 6; ++i)
	{
		printf("  %i: %6i %6i %6i %6i\n", i, (int)stats->varintCountsOp[i], (int)stats->varintCountsType[i], (int)stats->varintCountsRes[i], (int)stats->varintCountsOther[i]);
	}
	printf("Largest total size of ops in SMOL:\n");
	for (int i = 0; i < 30; ++i)
	{
		SpvOp op = sizesSmol[i].first;
		printf(" #%2i: %-22s %6i (%4.1f%%) avg len %.1f\n",
			   i,
			   kSpirvOpNames[op],
			   (int)sizesSmol[i].second,
			   (float)sizesSmol[i].second / (float)stats->totalSizeSmol * 100.0f,
			   (float)sizesSmol[i].second / (float)stats->opCounts[op]
		);
	}	
}


// ------------------------------------------------------------------------------
// This software is available under 2 licenses -- choose whichever you prefer.
// ------------------------------------------------------------------------------
// ALTERNATIVE A - MIT License
// Copyright (c) 2016-2024 Aras Pranckevicius
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ------------------------------------------------------------------------------
// ALTERNATIVE B - Public Domain (www.unlicense.org)
// This is free and unencumbered software released into the public domain.
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
// software, either in source code form or as a compiled binary, for any purpose,
// commercial or non-commercial, and by any means.
// In jurisdictions that recognize copyright laws, the author or authors of this
// software dedicate any and all copyright interest in the software to the public
// domain. We make this dedication for the benefit of the public at large and to
// the detriment of our heirs and successors. We intend this dedication to be an
// overt act of relinquishment in perpetuity of all present and future rights to
// this software under copyright law.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ------------------------------------------------------------------------------
