// File: j-faria[P]kima/eigen/Eigen/src/Core/products/GeneralBlockPanelKernel.h
// Function: void Eigen::internal::evaluateProductBlockingSizesHeuristic<double, double, 4, long>(long&, long&, long&, long)
void evaluateProductBlockingSizesHeuristic(Index& k, Index& m, Index& n, Index num_threads = 1)
{
  typedef gebp_traits<LhsScalar,RhsScalar> Traits;

  // Explanations:
  // Let's recall that the product algorithms form mc x kc vertical panels A' on the lhs and
  // kc x nc blocks B' on the rhs. B' has to fit into L2/L3 cache. Moreover, A' is processed
  // per mr x kc horizontal small panels where mr is the blocking size along the m dimension
  // at the register level. This small horizontal panel has to stay within L1 cache.
  std::ptrdiff_t l1, l2, l3;
  manage_caching_sizes(GetAction, &l1, &l2, &l3);
  #ifdef EIGEN_VECTORIZE_AVX512
  // We need to find a rationale for that, but without this adjustment,
  // performance with AVX512 is pretty bad, like -20% slower.
  // One reason is that with increasing packet-size, the blocking size k
  // has to become pretty small if we want that 1 lhs panel fit within L1.
  // For instance, with the 3pX4 kernel and double, the size of the lhs+rhs panels are:
  //   k*(3*64 + 4*8) Bytes, with l1=32kBytes, and k%8=0, we have k=144.
  // This is quite small for a good reuse of the accumulation registers.
  l1 *= 4;
  #endif

  if (num_threads > 1) {
    typedef typename Traits::ResScalar ResScalar;
    enum {
      kdiv = KcFactor * (Traits::mr * sizeof(LhsScalar) + Traits::nr * sizeof(RhsScalar)),
      ksub = Traits::mr * Traits::nr * sizeof(ResScalar),
      kr = 8,
      mr = Traits::mr,
      nr = Traits::nr
    };
    // Increasing k gives us more time to prefetch the content of the "C"
    // registers. However once the latency is hidden there is no point in
    // increasing the value of k, so we'll cap it at 320 (value determined
    // experimentally).
    const Index k_cache = (numext::mini<Index>)((l1-ksub)/kdiv, 320);
    if (k_cache < k) {
      k = k_cache - (k_cache % kr);
      eigen_internal_assert(k > 0);
    }

    const Index n_cache = (l2-l1) / (nr * sizeof(RhsScalar) * k);
    const Index n_per_thread = numext::div_ceil(n, num_threads);
    if (n_cache <= n_per_thread) {
      // Don't exceed the capacity of the l2 cache.
      eigen_internal_assert(n_cache >= static_cast<Index>(nr));
      n = n_cache - (n_cache % nr);
      eigen_internal_assert(n > 0);
    } else {
      n = (numext::mini<Index>)(n, (n_per_thread + nr - 1) - ((n_per_thread + nr - 1) % nr));
    }

    if (l3 > l2) {
      // l3 is shared between all cores, so we'll give each thread its own chunk of l3.
      const Index m_cache = (l3-l2) / (sizeof(LhsScalar) * k * num_threads);
      const Index m_per_thread = numext::div_ceil(m, num_threads);
      if(m_cache < m_per_thread && m_cache >= static_cast<Index>(mr)) {
        m = m_cache - (m_cache % mr);
        eigen_internal_assert(m > 0);
      } else {
        m = (numext::mini<Index>)(m, (m_per_thread + mr - 1) - ((m_per_thread + mr - 1) % mr));
      }
    }
  }
  else {
    // In unit tests we do not want to use extra large matrices,
    // so we reduce the cache size to check the blocking strategy is not flawed
#ifdef EIGEN_DEBUG_SMALL_PRODUCT_BLOCKS
    l1 = 9*1024;
    l2 = 32*1024;
    l3 = 512*1024;
#endif

    // Early return for small problems because the computation below are time consuming for small problems.
    // Perhaps it would make more sense to consider k*n*m??
    // Note that for very tiny problem, this function should be bypassed anyway
    // because we use the coefficient-based implementation for them.
    if((numext::maxi)(k,(numext::maxi)(m,n))<48)
      return;

    typedef typename Traits::ResScalar ResScalar;
    enum {
      k_peeling = 8,
      k_div = KcFactor * (Traits::mr * sizeof(LhsScalar) + Traits::nr * sizeof(RhsScalar)),
      k_sub = Traits::mr * Traits::nr * sizeof(ResScalar)
    };

    // ---- 1st level of blocking on L1, yields kc ----

    // Blocking on the third dimension (i.e., k) is chosen so that an horizontal panel
    // of size mr x kc of the lhs plus a vertical panel of kc x nr of the rhs both fits within L1 cache.
    // We also include a register-level block of the result (mx x nr).
    // (In an ideal world only the lhs panel would stay in L1)
    // Moreover, kc has to be a multiple of 8 to be compatible with loop peeling, leading to a maximum blocking size of:
    const Index max_kc = numext::maxi<Index>(((l1-k_sub)/k_div) & (~(k_peeling-1)),1);
    const Index old_k = k;
    if(k>max_kc)
    {
      // We are really blocking on the third dimension:
      // -> reduce blocking size to make sure the last block is as large as possible
      //    while keeping the same number of sweeps over the result.
      k = (k%max_kc)==0 ? max_kc
                        : max_kc - k_peeling * ((max_kc-1-(k%max_kc))/(k_peeling*(k/max_kc+1)));

      eigen_internal_assert(((old_k/k) == (old_k/max_kc)) && "the number of sweeps has to remain the same");
    }

    // ---- 2nd level of blocking on max(L2,L3), yields nc ----

    // TODO find a reliable way to get the actual amount of cache per core to use for 2nd level blocking, that is:
    //      actual_l2 = max(l2, l3/nb_core_sharing_l3)
    // The number below is quite conservative: it is better to underestimate the cache size rather than overestimating it)
    // For instance, it corresponds to 6MB of L3 shared among 4 cores.
    #ifdef EIGEN_DEBUG_SMALL_PRODUCT_BLOCKS
    const Index actual_l2 = l3;
    #else
    const Index actual_l2 = 1572864; // == 1.5 MB
    #endif

    // Here, nc is chosen such that a block of kc x nc of the rhs fit within half of L2.
    // The second half is implicitly reserved to access the result and lhs coefficients.
    // When k<max_kc, then nc can arbitrarily growth. In practice, it seems to be fruitful
    // to limit this growth: we bound nc to growth by a factor x1.5.
    // However, if the entire lhs block fit within L1, then we are not going to block on the rows at all,
    // and it becomes fruitful to keep the packed rhs blocks in L1 if there is enough remaining space.
    Index max_nc;
    const Index lhs_bytes = m * k * sizeof(LhsScalar);
    const Index remaining_l1 = l1- k_sub - lhs_bytes;
    if(remaining_l1 >= Index(Traits::nr*sizeof(RhsScalar))*k)
    {
      // L1 blocking
      max_nc = remaining_l1 / (k*sizeof(RhsScalar));
    }
    else
    {
      // L2 blocking
      max_nc = (3*actual_l2)/(2*2*max_kc*sizeof(RhsScalar));
    }
    // WARNING Below, we assume that Traits::nr is a power of two.
    Index nc = numext::mini<Index>(actual_l2/(2*k*sizeof(RhsScalar)), max_nc) & (~(Traits::nr-1));
    if(n>nc)
    {
      // We are really blocking over the columns:
      // -> reduce blocking size to make sure the last block is as large as possible
      //    while keeping the same number of sweeps over the packed lhs.
      //    Here we allow one more sweep if this gives us a perfect match, thus the commented "-1"
      n = (n%nc)==0 ? nc
                    : (nc - Traits::nr * ((nc/*-1*/-(n%nc))/(Traits::nr*(n/nc+1))));
    }
    else if(old_k==k)
    {
      // So far, no blocking at all, i.e., kc==k, and nc==n.
      // In this case, let's perform a blocking over the rows such that the packed lhs data is kept in cache L1/L2
      // TODO: part of this blocking strategy is now implemented within the kernel itself, so the L1-based heuristic here should be obsolete.
      Index problem_size = k*n*sizeof(LhsScalar);
      Index actual_lm = actual_l2;
      Index max_mc = m;
      if(problem_size<=1024)
      {
        // problem is small enough to keep in L1
        // Let's choose m such that lhs's block fit in 1/3 of L1
        actual_lm = l1;
      }
      else if(l3!=0 && problem_size<=32768)
      {
        // we have both L2 and L3, and problem is small enough to be kept in L2
        // Let's choose m such that lhs's block fit in 1/3 of L2
        actual_lm = l2;
        max_mc = (numext::mini<Index>)(576,max_mc);
      }
      Index mc = (numext::mini<Index>)(actual_lm/(3*k*sizeof(LhsScalar)), max_mc);
      if (mc > Traits::mr) mc -= mc % Traits::mr;
      else if (mc==0) return;
      m = (m%mc)==0 ? mc
                    : (mc - Traits::mr * ((mc/*-1*/-(m%mc))/(Traits::mr*(m/mc+1))));
    }
  }
}

// Assembly:
pushq %rbp
movq %rsp, %rbp
subq $0x210, %rsp            # imm = 0x210
movq %rdi, -0x98(%rbp)
movq %rsi, -0xa0(%rbp)
movq %rdx, -0xa8(%rbp)
movq %rcx, -0xb0(%rbp)
xorl %edi, %edi
leaq -0xb8(%rbp), %rsi
leaq -0xc0(%rbp), %rdx
leaq -0xc8(%rbp), %rcx
callq 0x41b40
cmpq $0x1, -0xb0(%rbp)
jle 0x4157e
movq -0xb8(%rbp), %rax
subq $0x80, %rax
movl $0x100, %ecx            # imm = 0x100
cqto
idivq %rcx
movq %rax, -0xd8(%rbp)
movq $0x140, -0xe0(%rbp) # imm = 0x140
leaq -0xd8(%rbp), %rax
movq %rax, -0x38(%rbp)
leaq -0xe0(%rbp), %rax
movq %rax, -0x40(%rbp)
movq -0x38(%rbp), %rdi
movq -0x40(%rbp), %rsi
callq 0x7cf0
movq (%rax), %rax
movq %rax, -0xd0(%rbp)
movq -0xd0(%rbp), %rax
movq -0x98(%rbp), %rcx
cmpq (%rcx), %rax
jge 0x4137f
movq -0xd0(%rbp), %rcx
movq -0xd0(%rbp), %rax
movl $0x8, %esi
cqto
idivq %rsi
subq %rdx, %rcx
movq -0x98(%rbp), %rax
movq %rcx, (%rax)
movq -0xc0(%rbp), %rax
subq -0xb8(%rbp), %rax
movq -0x98(%rbp), %rcx
movq (%rcx), %rcx
shlq $0x5, %rcx
xorl %edx, %edx
divq %rcx
movq %rax, -0xe8(%rbp)
movq -0xa8(%rbp), %rdi
leaq -0xb0(%rbp), %rsi
callq 0x41c20
movq %rax, -0xf0(%rbp)
movq -0xe8(%rbp), %rax
cmpq -0xf0(%rbp), %rax
jg 0x413f8
movq -0xe8(%rbp), %rcx
movq -0xe8(%rbp), %rax
movl $0x4, %esi
cqto
idivq %rsi
subq %rdx, %rcx
movq -0xa8(%rbp), %rax
movq %rcx, (%rax)
jmp 0x41468
movq -0xa8(%rbp), %rax
movq %rax, -0x1a0(%rbp)
movq -0xf0(%rbp), %rcx
addq $0x4, %rcx
subq $0x1, %rcx
movq -0xf0(%rbp), %rax
addq $0x4, %rax
subq $0x1, %rax
movl $0x4, %esi
cqto
idivq %rsi
movq -0x1a0(%rbp), %rax
subq %rdx, %rcx
movq %rcx, -0xf8(%rbp)
movq %rax, -0x48(%rbp)
leaq -0xf8(%rbp), %rax
movq %rax, -0x50(%rbp)
movq -0x48(%rbp), %rdi
movq -0x50(%rbp), %rsi
callq 0x7cf0
movq (%rax), %rcx
movq -0xa8(%rbp), %rax
movq %rcx, (%rax)
movq -0xc8(%rbp), %rax
cmpq -0xc0(%rbp), %rax
jle 0x41579
movq -0xc8(%rbp), %rax
subq -0xc0(%rbp), %rax
movq -0x98(%rbp), %rcx
movq (%rcx), %rcx
shlq $0x3, %rcx
imulq -0xb0(%rbp), %rcx
xorl %edx, %edx
divq %rcx
movq %rax, -0x100(%rbp)
movq -0xa0(%rbp), %rdi
leaq -0xb0(%rbp), %rsi
callq 0x41c20
movq %rax, -0x108(%rbp)
movq -0x100(%rbp), %rax
cmpq -0x108(%rbp), %rax
jge 0x41507
cmpq $0x4, -0x100(%rbp)
jl 0x41507
movq -0x100(%rbp), %rcx
movq -0x100(%rbp), %rax
movl $0x4, %esi
cqto
idivq %rsi
subq %rdx, %rcx
movq -0xa0(%rbp), %rax
movq %rcx, (%rax)
jmp 0x41577
movq -0xa0(%rbp), %rax
movq %rax, -0x1a8(%rbp)
movq -0x108(%rbp), %rcx
addq $0x4, %rcx
subq $0x1, %rcx
movq -0x108(%rbp), %rax
addq $0x4, %rax
subq $0x1, %rax
movl $0x4, %esi
cqto
idivq %rsi
movq -0x1a8(%rbp), %rax
subq %rdx, %rcx
movq %rcx, -0x110(%rbp)
movq %rax, -0x58(%rbp)
leaq -0x110(%rbp), %rax
movq %rax, -0x60(%rbp)
movq -0x58(%rbp), %rdi
movq -0x60(%rbp), %rsi
callq 0x7cf0
movq (%rax), %rcx
movq -0xa0(%rbp), %rax
movq %rcx, (%rax)
jmp 0x41579
jmp 0x41b2c
movq -0x98(%rbp), %rax
movq %rax, -0x1b0(%rbp)
movq -0xa0(%rbp), %rcx
movq -0xa8(%rbp), %rax
movq %rcx, -0x8(%rbp)
movq %rax, -0x10(%rbp)
movq -0x8(%rbp), %rdi
movq -0x10(%rbp), %rsi
callq 0x7cb0
movq %rax, %rcx
movq -0x1b0(%rbp), %rax
movq (%rcx), %rcx
movq %rcx, -0x118(%rbp)
movq %rax, -0x18(%rbp)
leaq -0x118(%rbp), %rax
movq %rax, -0x20(%rbp)
movq -0x18(%rbp), %rdi
movq -0x20(%rbp), %rsi
callq 0x7cb0
cmpq $0x30, (%rax)
jge 0x415ea
jmp 0x41b2c
movq -0xb8(%rbp), %rax
subq $0x80, %rax
movl $0x100, %ecx            # imm = 0x100
cqto
idivq %rcx
andq $-0x8, %rax
movq %rax, -0x128(%rbp)
movq $0x1, -0x130(%rbp)
leaq -0x128(%rbp), %rax
movq %rax, -0x28(%rbp)
leaq -0x130(%rbp), %rax
movq %rax, -0x30(%rbp)
movq -0x28(%rbp), %rdi
movq -0x30(%rbp), %rsi
callq 0x7cb0
movq (%rax), %rax
movq %rax, -0x120(%rbp)
movq -0x98(%rbp), %rax
movq (%rax), %rax
movq %rax, -0x138(%rbp)
movq -0x98(%rbp), %rax
movq (%rax), %rax
cmpq -0x120(%rbp), %rax
jle 0x4172f
movq -0x98(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x120(%rbp)
cmpq $0x0, %rdx
jne 0x41698
movq -0x120(%rbp), %rax
movq %rax, -0x1b8(%rbp)
jmp 0x4171e
movq -0x120(%rbp), %rax
movq %rax, -0x1c0(%rbp)
movq -0x120(%rbp), %rax
subq $0x1, %rax
movq %rax, -0x1d0(%rbp)
movq -0x98(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x120(%rbp)
movq -0x1d0(%rbp), %rax
subq %rdx, %rax
movq %rax, -0x1c8(%rbp)
movq -0x98(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x120(%rbp)
movq %rax, %rcx
movq -0x1c8(%rbp), %rax
addq $0x1, %rcx
shlq $0x3, %rcx
cqto
idivq %rcx
movq %rax, %rcx
movq -0x1c0(%rbp), %rax
shlq $0x3, %rcx
subq %rcx, %rax
movq %rax, -0x1b8(%rbp)
movq -0x1b8(%rbp), %rcx
movq -0x98(%rbp), %rax
movq %rcx, (%rax)
movq $0x180000, -0x140(%rbp) # imm = 0x180000
movq -0xa0(%rbp), %rax
movq (%rax), %rax
movq -0x98(%rbp), %rcx
imulq (%rcx), %rax
shlq $0x3, %rax
movq %rax, -0x150(%rbp)
movq -0xb8(%rbp), %rax
subq $0x80, %rax
subq -0x150(%rbp), %rax
movq %rax, -0x158(%rbp)
movq -0x158(%rbp), %rax
movq -0x98(%rbp), %rcx
movq (%rcx), %rcx
shlq $0x5, %rcx
cmpq %rcx, %rax
jl 0x417b2
movq -0x158(%rbp), %rax
movq -0x98(%rbp), %rcx
movq (%rcx), %rcx
shlq $0x3, %rcx
xorl %edx, %edx
divq %rcx
movq %rax, -0x148(%rbp)
jmp 0x417d2
movq -0x120(%rbp), %rcx
shlq $0x2, %rcx
shlq $0x3, %rcx
movl $0x480000, %eax         # imm = 0x480000
xorl %edx, %edx
divq %rcx
movq %rax, -0x148(%rbp)
movq -0x98(%rbp), %rax
movq (%rax), %rcx
shlq %rcx
shlq $0x3, %rcx
movl $0x180000, %eax         # imm = 0x180000
xorl %edx, %edx
divq %rcx
movq %rax, -0x168(%rbp)
leaq -0x168(%rbp), %rax
movq %rax, -0x68(%rbp)
leaq -0x148(%rbp), %rax
movq %rax, -0x70(%rbp)
movq -0x68(%rbp), %rdi
movq -0x70(%rbp), %rsi
callq 0x7cf0
movq (%rax), %rax
andq $-0x4, %rax
movq %rax, -0x160(%rbp)
movq -0xa8(%rbp), %rax
movq (%rax), %rax
cmpq -0x160(%rbp), %rax
jle 0x41900
movq -0xa8(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x160(%rbp)
cmpq $0x0, %rdx
jne 0x41868
movq -0x160(%rbp), %rax
movq %rax, -0x1d8(%rbp)
jmp 0x418ea
movq -0x160(%rbp), %rax
movq %rax, -0x1e0(%rbp)
movq -0x160(%rbp), %rax
movq %rax, -0x1f0(%rbp)
movq -0xa8(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x160(%rbp)
movq -0x1f0(%rbp), %rax
subq %rdx, %rax
movq %rax, -0x1e8(%rbp)
movq -0xa8(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x160(%rbp)
movq %rax, %rcx
movq -0x1e8(%rbp), %rax
addq $0x1, %rcx
shlq $0x2, %rcx
cqto
idivq %rcx
movq %rax, %rcx
movq -0x1e0(%rbp), %rax
shlq $0x2, %rcx
subq %rcx, %rax
movq %rax, -0x1d8(%rbp)
movq -0x1d8(%rbp), %rcx
movq -0xa8(%rbp), %rax
movq %rcx, (%rax)
jmp 0x41b2a
movq -0x138(%rbp), %rax
movq -0x98(%rbp), %rcx
cmpq (%rcx), %rax
jne 0x41b28
movq -0x98(%rbp), %rax
movq (%rax), %rax
movq -0xa8(%rbp), %rcx
imulq (%rcx), %rax
shlq $0x3, %rax
movq %rax, -0x170(%rbp)
movq $0x180000, -0x178(%rbp) # imm = 0x180000
movq -0xa0(%rbp), %rax
movq (%rax), %rax
movq %rax, -0x180(%rbp)
cmpq $0x400, -0x170(%rbp) # imm = 0x400
jg 0x41970
movq -0xb8(%rbp), %rax
movq %rax, -0x178(%rbp)
jmp 0x419cf
cmpq $0x0, -0xc8(%rbp)
je 0x419cd
cmpq $0x8000, -0x170(%rbp) # imm = 0x8000
jg 0x419cd
movq -0xc0(%rbp), %rax
movq %rax, -0x178(%rbp)
movq $0x240, -0x188(%rbp) # imm = 0x240
leaq -0x188(%rbp), %rax
movq %rax, -0x78(%rbp)
leaq -0x180(%rbp), %rax
movq %rax, -0x80(%rbp)
movq -0x78(%rbp), %rdi
movq -0x80(%rbp), %rsi
callq 0x7cf0
movq (%rax), %rax
movq %rax, -0x180(%rbp)
jmp 0x419cf
movq -0x178(%rbp), %rax
movq -0x98(%rbp), %rcx
imulq $0x3, (%rcx), %rcx
shlq $0x3, %rcx
xorl %edx, %edx
divq %rcx
movq %rax, -0x198(%rbp)
leaq -0x198(%rbp), %rax
movq %rax, -0x88(%rbp)
leaq -0x180(%rbp), %rax
movq %rax, -0x90(%rbp)
movq -0x88(%rbp), %rdi
movq -0x90(%rbp), %rsi
callq 0x7cf0
movq (%rax), %rax
movq %rax, -0x190(%rbp)
cmpq $0x4, -0x190(%rbp)
jle 0x41a58
movq -0x190(%rbp), %rax
movl $0x4, %ecx
cqto
idivq %rcx
movq -0x190(%rbp), %rax
subq %rdx, %rax
movq %rax, -0x190(%rbp)
jmp 0x41a69
cmpq $0x0, -0x190(%rbp)
jne 0x41a67
jmp 0x41b2c
jmp 0x41a69
movq -0xa0(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x190(%rbp)
cmpq $0x0, %rdx
jne 0x41a95
movq -0x190(%rbp), %rax
movq %rax, -0x1f8(%rbp)
jmp 0x41b17
movq -0x190(%rbp), %rax
movq %rax, -0x200(%rbp)
movq -0x190(%rbp), %rax
movq %rax, -0x210(%rbp)
movq -0xa0(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x190(%rbp)
movq -0x210(%rbp), %rax
subq %rdx, %rax
movq %rax, -0x208(%rbp)
movq -0xa0(%rbp), %rax
movq (%rax), %rax
cqto
idivq -0x190(%rbp)
movq %rax, %rcx
movq -0x208(%rbp), %rax
addq $0x1, %rcx
shlq $0x2, %rcx
cqto
idivq %rcx
movq %rax, %rcx
movq -0x200(%rbp), %rax
shlq $0x2, %rcx
subq %rcx, %rax
movq %rax, -0x1f8(%rbp)
movq -0x1f8(%rbp), %rcx
movq -0xa0(%rbp), %rax
movq %rcx, (%rax)
jmp 0x41b2a
jmp 0x41b2c
addq $0x210, %rsp            # imm = 0x210
popq %rbp
retq
nopw %cs:(%rax,%rax)
nop