Thanks, I've skimmed though it and have a few questions and comments. I know very little about RISC-V, but I've recently been diving into issues of macro- and micro-fusion on recent Intel x64.

The first question would be: Why do we care about instruction counts? Historically it's been used as a proxy for computational cost, but as you point out it no longer correlates that well. Additionally, many modern processors have µop caches that greatly reduce the importance of the initial instructions. But instead of suggesting that we ignore instruction count, the paper seems to suggest that we need to consider both instruction and µop count. Why?

You mention that "Compiler tool chains are a continual work-in-progress" and mention that you "used GCC for all targets as it is widely used and the only compiler available for all systems". This makes me wonder how much you are testing the differences in ISA's versus how much you are just testing GCC's ability to optimize for each.

My strong impression is that GCC does a poor job of optimizing macro- or micro-fusion for x64. It seems mostly oblivious to the issues, and the efficiency of the resulting code is equally likely to be anywhere from the same speed as microarchitecture conscious assembly to 2x slower. Did you attempt to correct for this when choosing x64 as the baseline?

For example, I noticed the x64 code in 401.bzip in Code 2:

n = ((Int32)block[ptr[unHi]+d]) - med;

4039d0: mov (%r10), %edx
4039d3: lea (%r15,%rdx,1), %eax
4039d7: movzbl (%r14,%rax,1), %eax
4039dc: sub %r9d, %eax
4039df: cmp $0x0, %eax
4039e2: jne 403a8a

While some of this is due to the requirements of the specific integer widths (intentionally? arbitrarily? accidentally?) specified in the source, GCC seems to have come up with a very roundabout solution. Why a MOV/LEA rather than a micro-fused ADD load-op? For that matter, why use an LEA (which is restricted to fewer execution ports) rather than an ADD?

And while in this case it happened to keep the CMP/JNE fusible, why does it have a CMP there at all, instead of using the flags already set by the SUB? From Sandy Bridge onward, SUB/JNE is also a macro-fused single instruction, so instead of two instructions fusing to one we have three instructions fusing to two.

There are probably reasons for all this, but it does make it it awkward as a baseline. Whether it makes it to the paper or not, verifying that the baseline x64 numbers don't change significantly with Clang or ICC with a variety of optimization flags might be useful. If you were heroic, comparing to a version with hand-optimized would be interesting as well.

A last comment is that I recently realized there were some significant differences between the Haswell and Skylake generations with regard to the effects of fusion on performance. A loop like this:

#define ASM_MICRO_MACRO(in1, sum1, in2, sum2, max) \
__asm volatile ("1:\n" \
"add (%[IN1]), %[SUM1]\n" \
"cmp %[MAX], %[SUM1]\n" \
"jae 2f\n" \
"add (%[IN2]), %[SUM2]\n" \
"cmp %[MAX], %[SUM2]\n" \
"jb 1b\n" \
"2:" : \
[SUM1] "+&r" (sum1), \
[SUM2] "+&r" (sum2) : \
[IN1] "r" (in1), \
[IN2] "r" (in2), \
[MAX] "r" (max))

Ignoring that this isn't a particularly useful loop, it executes in at a single cycle per loop iteration on Skylake, but takes 1.8 cycles per iteration on Haswell. The amount of fusion is the same on each, but the performance differs greatly. I'm not yet sure yet of the specific reasons, but it makes me wonder if the choice of Ivy Bridge as the reference implementation for x64 might be influence your conclusions (although I don't know in which direction).

￼￼￼￼￼￼