/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */


#pragma once

#include <faiss/gpu/utils/PtxUtils.cuh>

namespace faiss { namespace gpu {

#if __CUDA_ARCH__ >= 350
// Use the CC 3.5+ read-only texture cache (nc)
#define LD_NC_V1 "ld.global.cs.nc.u32"
#define LD_NC_V2 "ld.global.cs.nc.v2.u32"
#define LD_NC_V4 "ld.global.cs.nc.v4.u32"
#else
// Read normally
#define LD_NC_V1 "ld.global.cs.u32"
#define LD_NC_V2 "ld.global.cs.v2.u32"
#define LD_NC_V4 "ld.global.cs.v4.u32"
#endif // __CUDA_ARCH__

///
/// This file contains loader functions for PQ codes of various byte
/// length.
///

// Type-specific wrappers around the PTX bfe.* instruction, for
// quantization code extraction
inline __device__ unsigned int getByte(unsigned char v,
                                       int pos,
                                       int width) {
  return v;
}

inline __device__ unsigned int getByte(unsigned short v,
                                       int pos,
                                       int width) {
  return getBitfield((unsigned int) v, pos, width);
}

inline __device__ unsigned int getByte(unsigned int v,
                                       int pos,
                                       int width) {
  return getBitfield(v, pos, width);
}

inline __device__ unsigned int getByte(uint64_t v,
                                       int pos,
                                       int width) {
  return getBitfield(v, pos, width);
}

template <int NumSubQuantizers>
struct LoadCode32 {};

template<>
struct LoadCode32<1> {
  static inline __device__ void load(unsigned int code32[1],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 1;
    asm("ld.global.cs.u8 {%0}, [%1];" :
        "=r"(code32[0]) : "l"(p));
  }
};

template<>
struct LoadCode32<2> {
  static inline __device__ void load(unsigned int code32[1],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 2;
    asm("ld.global.cs.u16 {%0}, [%1];" :
        "=r"(code32[0]) : "l"(p));
  }
};

template<>
struct LoadCode32<3> {
  static inline __device__ void load(unsigned int code32[1],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 3;
    unsigned int a;
    unsigned int b;
    unsigned int c;

    // FIXME: this is a non-coalesced, unaligned, non-vectorized load
    // unfortunately need to reorganize memory layout by warp
    asm("ld.global.cs.u8 {%0}, [%1 + 0];" :
        "=r"(a) : "l"(p));
    asm("ld.global.cs.u8 {%0}, [%1 + 1];" :
        "=r"(b) : "l"(p));
    asm("ld.global.cs.u8 {%0}, [%1 + 2];" :
        "=r"(c) : "l"(p));

    // FIXME: this is also slow, since we have to recover the
    // individual bytes loaded
    code32[0] = (c << 16) | (b << 8) | a;
  }
};

template<>
struct LoadCode32<4> {
  static inline __device__ void load(unsigned int code32[1],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 4;
    asm("ld.global.cs.u32 {%0}, [%1];" :
        "=r"(code32[0]) : "l"(p));
  }
};

template<>
struct LoadCode32<8> {
  static inline __device__ void load(unsigned int code32[2],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 8;
    asm("ld.global.cs.v2.u32 {%0, %1}, [%2];" :
        "=r"(code32[0]), "=r"(code32[1]) : "l"(p));
  }
};

template<>
struct LoadCode32<12> {
  static inline __device__ void load(unsigned int code32[3],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 12;
    // FIXME: this is a non-coalesced, unaligned, non-vectorized load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V1 " {%0}, [%1 + 0];" :
        "=r"(code32[0]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 4];" :
        "=r"(code32[1]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 8];" :
        "=r"(code32[2]) : "l"(p));
  }
};

template<>
struct LoadCode32<16> {
  static inline __device__ void load(unsigned int code32[4],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 16;
    asm("ld.global.cs.v4.u32 {%0, %1, %2, %3}, [%4];" :
        "=r"(code32[0]), "=r"(code32[1]),
        "=r"(code32[2]), "=r"(code32[3]) : "l"(p));
  }
};

template<>
struct LoadCode32<20> {
  static inline __device__ void load(unsigned int code32[5],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 20;
    // FIXME: this is a non-coalesced, unaligned, non-vectorized load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V1 " {%0}, [%1 + 0];" :
        "=r"(code32[0]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 4];" :
        "=r"(code32[1]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 8];" :
        "=r"(code32[2]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 12];" :
        "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 16];" :
        "=r"(code32[4]) : "l"(p));
  }
};

template<>
struct LoadCode32<24> {
  static inline __device__ void load(unsigned int code32[6],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 24;
    // FIXME: this is a non-coalesced, unaligned, 2-vectorized load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V2 " {%0, %1}, [%2 + 0];" :
        "=r"(code32[0]), "=r"(code32[1]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 8];" :
        "=r"(code32[2]), "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 16];" :
        "=r"(code32[4]), "=r"(code32[5]) : "l"(p));
  }
};

template<>
struct LoadCode32<28> {
  static inline __device__ void load(unsigned int code32[7],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 28;
    // FIXME: this is a non-coalesced, unaligned, non-vectorized load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V1 " {%0}, [%1 + 0];" :
        "=r"(code32[0]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 4];" :
        "=r"(code32[1]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 8];" :
        "=r"(code32[2]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 12];" :
        "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 16];" :
        "=r"(code32[4]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 20];" :
        "=r"(code32[5]) : "l"(p));
    asm(LD_NC_V1 " {%0}, [%1 + 24];" :
        "=r"(code32[6]) : "l"(p));
  }
};

template<>
struct LoadCode32<32> {
  static inline __device__ void load(unsigned int code32[8],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 32;
    // FIXME: this is a non-coalesced load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4];" :
        "=r"(code32[0]), "=r"(code32[1]),
        "=r"(code32[2]), "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 16];" :
        "=r"(code32[4]), "=r"(code32[5]),
        "=r"(code32[6]), "=r"(code32[7]) : "l"(p));
  }
};

template<>
struct LoadCode32<40> {
  static inline __device__ void load(unsigned int code32[10],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 40;
    // FIXME: this is a non-coalesced, unaligned, 2-vectorized load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V2 " {%0, %1}, [%2 + 0];" :
        "=r"(code32[0]), "=r"(code32[1]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 8];" :
        "=r"(code32[2]), "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 16];" :
        "=r"(code32[4]), "=r"(code32[5]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 24];" :
        "=r"(code32[6]), "=r"(code32[7]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 32];" :
        "=r"(code32[8]), "=r"(code32[9]) : "l"(p));
  }
};

template<>
struct LoadCode32<48> {
  static inline __device__ void load(unsigned int code32[12],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 48;
    // FIXME: this is a non-coalesced load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4];" :
        "=r"(code32[0]), "=r"(code32[1]),
        "=r"(code32[2]), "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 16];" :
        "=r"(code32[4]), "=r"(code32[5]),
        "=r"(code32[6]), "=r"(code32[7]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 32];" :
        "=r"(code32[8]), "=r"(code32[9]),
        "=r"(code32[10]), "=r"(code32[11]) : "l"(p));
  }
};

template<>
struct LoadCode32<56> {
  static inline __device__ void load(unsigned int code32[14],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 56;
    // FIXME: this is a non-coalesced, unaligned, 2-vectorized load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V2 " {%0, %1}, [%2 + 0];" :
        "=r"(code32[0]), "=r"(code32[1]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 8];" :
        "=r"(code32[2]), "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 16];" :
        "=r"(code32[4]), "=r"(code32[5]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 24];" :
        "=r"(code32[6]), "=r"(code32[7]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 32];" :
        "=r"(code32[8]), "=r"(code32[9]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 40];" :
        "=r"(code32[10]), "=r"(code32[11]) : "l"(p));
    asm(LD_NC_V2 " {%0, %1}, [%2 + 48];" :
        "=r"(code32[12]), "=r"(code32[13]) : "l"(p));
  }
};

template<>
struct LoadCode32<64> {
  static inline __device__ void load(unsigned int code32[16],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 64;
    // FIXME: this is a non-coalesced load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4];" :
        "=r"(code32[0]), "=r"(code32[1]),
        "=r"(code32[2]), "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 16];" :
        "=r"(code32[4]), "=r"(code32[5]),
        "=r"(code32[6]), "=r"(code32[7]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 32];" :
        "=r"(code32[8]), "=r"(code32[9]),
        "=r"(code32[10]), "=r"(code32[11]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 48];" :
        "=r"(code32[12]), "=r"(code32[13]),
        "=r"(code32[14]), "=r"(code32[15]) : "l"(p));
  }
};

template<>
struct LoadCode32<96> {
  static inline __device__ void load(unsigned int code32[24],
                                     uint8_t* p,
                                     int offset) {
    p += offset * 96;
    // FIXME: this is a non-coalesced load
    // unfortunately need to reorganize memory layout by warp
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4];" :
        "=r"(code32[0]), "=r"(code32[1]),
        "=r"(code32[2]), "=r"(code32[3]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 16];" :
        "=r"(code32[4]), "=r"(code32[5]),
        "=r"(code32[6]), "=r"(code32[7]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 32];" :
        "=r"(code32[8]), "=r"(code32[9]),
        "=r"(code32[10]), "=r"(code32[11]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 48];" :
        "=r"(code32[12]), "=r"(code32[13]),
        "=r"(code32[14]), "=r"(code32[15]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 64];" :
        "=r"(code32[16]), "=r"(code32[17]),
        "=r"(code32[18]), "=r"(code32[19]) : "l"(p));
    asm(LD_NC_V4 " {%0, %1, %2, %3}, [%4 + 80];" :
        "=r"(code32[20]), "=r"(code32[21]),
        "=r"(code32[22]), "=r"(code32[23]) : "l"(p));
  }
};

} } // namespace
