OffTarget

Observed issues › hecbench/gcc-c-swar-popcount-assembler-error

Appears to behave incorrectlyCompiler crash · C · OpenMP · Open

GCC: invalid assembly for a bit-counting loop on MI250X

Legality: independently confirmed. 2 independent readings find the program legal and the result required. How this is checked

GCC 16.2 for gfx90a emits a v_bcnt_u32_b32 instruction with a missing operand for the parallel-sum popcount loop in a target region, so the offload link fails; shift-and-add and the builtin compile.

Affected variants

CompilerMI300A gfx942
unified memory (XNACK 1)
MI300A gfx942
copy (XNACK 0)
MI250X gfx90a
copy (XNACK 0)
MI300X gfx942
copy (XNACK 0)
MI350X gfx950
copy (XNACK 0)
A100 sm_80
separate memory
A100 sm_80
managed memory
AFAR 24.3.0correctcorrectunknown
not run
correctcorrectn/an/a
CCE 19.0.0correctcorrectunknown
not run
unknown
not run
unknown
not run
n/an/a
CCE 20.0.2correctcorrectunknown
not run
unknown
not run
unknown
not run
n/an/a
CCE 21.0.3correctcorrectunknown
not run
unknown
not run
unknown
not run
n/an/a
CCE 21.0.4correctcorrectunknown
not run
unknown
not run
unknown
not run
n/an/a
NVHPC 25.11n/an/an/an/an/acorrectcorrect
NVHPC 26.9n/an/an/an/an/acorrectcorrect
GCC 16.2.0behaves incorrectlybehaves incorrectlybehaves incorrectlybehaves incorrectlybehaves incorrectlycorrectn/a

"behaves incorrectly" / "not supported": this issue, confirmed in up to four environments (another build directory, another node, a repeat). "fails another way": a different failure, not counted as this issue. "unknown": not run there, or the runs disagree.

Messages

ResultCompiler and GPUMessage
behaves incorrectlyGCC 16.2.0, MI300A unified
GCC 16.2.0, MI300A copy
GCC 16.2.0, MI300X
GCC 16.2.0, MI350X
show
<dir>:129:2: error: too few operands for instruction
        v_bcnt_u32_b32  v0, v0
        ^
<dir>:145:2: error: too few operands for instruction
        v_bcnt_u32_b32  v3, v3
        ^
<dir>:181:2: error: too few operands for instruction
        v_bcnt_u32_b32  v0, v0
behaves incorrectlyGCC 16.2.0, MI250X
show
<dir>:126:2: error: too few operands for instruction
        v_bcnt_u32_b32  v0, v0
        ^
<dir>:139:2: error: too few operands for instruction
        v_bcnt_u32_b32  v3, v3
        ^
<dir>:172:2: error: too few operands for instruction
        v_bcnt_u32_b32  v0, v0

Reproduce

GCC 16.2.0, MI300A unified

export LD_LIBRARY_PATH=$GCC_16_2/lib64:/opt/rocm-7.2.1/lib
$GCC_16_2/bin/gcc -fopenmp -foffload=amdgcn-amdhsa -foffload-options=amdgcn-amdhsa=-march=gfx942 -O2 repro.c -o repro
HSA_XNACK=1 OMP_TARGET_OFFLOAD=mandatory ./repro

AFAR 24.3.0, MI300A unified (runs correctly)

$AFAR/bin/amdclang -fopenmp --offload-arch=gfx942 -O2 repro.c -o repro
HSA_XNACK=1 OMP_TARGET_OFFLOAD=mandatory ./repro
Program (73 lines)
// # Finding (hecbench): GCC (gfx90a): a bit-counting loop in a target region makes the generated assembly invalid
// (v_bcnt_u32_b32)
//
// - toolchains and modes that fail: gcc_g90 (GCC 16.2.0); modes: copy; -O2
// - build: gcc -fopenmp -foffload=amdgcn-amdhsa -foffload-options=amdgcn-amdhsa=-march=gfx90a -O2
// - failure: ice
// - observed: `<tmpdir>/ccRlCeSn.mkoffload.2.s:126:2: error: too few operands for instruction`; identical signature on 1 of 1
//   failing cells
// - expected: compiles and prints `bad= 0` with `check=pass`
// - rule: "The functionality added to the target directive is the inclusion of an executable region to be executed on a device"
//   (5.2 §13.8 p.284 l.12-13): the region is a loop of integer shifts, masks and one multiplication (the parallel-sum popcount
//   idiom); the specification limits no integer operation in a target region, and with the last step written as shifts and adds,
//   or as __builtin_popcountll, the compile succeeds
// - same-compiler control: controls/pc-ctl-shift-sum.c (one change from this file; correct on gcc_g90 (GCC 16.2.0); rows
//   results/hecbench/red/tio_g90.jsonl, results/hecbench/red/tuo.jsonl, results/hecbench/red/wt1.jsonl);
//   controls/pc-ctl-builtin-popcount.c (one change from this file; correct on gcc_g90 (GCC 16.2.0); rows
//   results/hecbench/red/tio_g90.jsonl, results/hecbench/red/tuo.jsonl, results/hecbench/red/wt1.jsonl)
// - other families: afar (AFAR 24.3.0), cce19 (CCE 19.0.0), cce20 (CCE 20.0.2), cce21 (CCE 21.0.3), cce21_4 (CCE 21.0.4), nvhpc
//   (NVHPC 25.11), nvhpc269 (NVHPC 26.9), gcc_sm80 (GCC 16.2.0) run the file correctly (rows results/hecbench/red/tio_g90.jsonl,
//   results/hecbench/red/tuo.jsonl, results/hecbench/red/wt1.jsonl)
// - gate and confirmer: campaign gate of the programs below: 1 cells, 1 confirmed in the four environments (a-d; (c) on another
//   node or GPU; rows results/hecbench/dev/gate_reruns.part.<host>.2090963.jsonl,
//   results/hecbench/dev/gate_reruns.part.<host>.2185748.jsonl); 1 of 1 cells reproduce in all four environments (a-d; (c) on
//   another node or GPU than (a); rows results/hecbench/red/tio_g90.jsonl, results/hecbench/red/tio_gate_abd.jsonl,
//   results/hecbench/red/tio_gate_c.jsonl)
// - origin: HeCBench `src/popcount-omp/main.cpp` (https://github.com/ORNL/HeCBench, commit
//   8f44ff3e69fa003f870963045988190a275534cb), whose gcc_g90 cell fails at the link with `error: too few operands for
//   instruction` in mkoffload's assembly. This file is a small program written for this finding and reduced with tools/reduce.py;
//   the bit-counting steps are the textbook parallel-sum popcount, and it holds no benchmark source.
// - programs (1): hecbench-popcount-omp
// - legality: offload.engine.prerun.check (the rules, and AFAR's front end on Tuolumne, 2026-11-06: two witnesses) accepts this file
//   and the controls; AFAR, CCE and NVHPC compile and run it correctly (front ends of three other families). GCC is a compiler
//   under test only and never a legality witness.
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

#define M1 0x5555555555555555UL
#define M2 0x3333333333333333UL
#define M4 0x0f0f0f0f0f0f0f0fUL
#define H01 0x0101010101010101UL

int main(void)
{
  const int length = 1024;
  int hf = 1, bad = 0;
  unsigned long *data = malloc(length * sizeof(unsigned long));
  int *result = malloc(length * sizeof(int));
  for (int i = 0; i < length; i++)
    data[i] = (unsigned long)(i + 1) * 0x9E3779B97F4A7C15UL;
  #pragma omp target map(from: hf)
  hf = omp_is_initial_device() ? 1 : 0;

  #pragma omp target map(to: data[0:length]) map(from: result[0:length])
  for (int i = 0; i < length; i++) {
    unsigned long x = data[i];
    x -= (x >> 1) & M1;
    x = (x & M2) + ((x >> 2) & M2);
    x = (x + (x >> 4)) & M4;
    result[i] = (x * H01) >> 56;
  }

  for (int i = 0; i < length; i++) {
    int count = 0;
    for (unsigned long y = data[i]; y; count++) y &= y - 1;
    if (count != result[i]) bad++;
  }
  printf("bad= %d\n", bad);
  printf("RESULT ran_on_host=%d check=%s\n", hf, bad == 0 ? "pass" : "fail");
  free(data);
  free(result);
  return 0;
}

Source in the repository

Something to add? Dispute its legality · Report it fixed in a newer release · Report a mistake on this page

Expected and observed

Observed where it behaves incorrectly

<dir>:129:2: error: too few operands for instruction
        v_bcnt_u32_b32  v0, v0
        ^
<dir>:145:2: error: too few operands for instruction
        v_bcnt_u32_b32  v3, v3
        ^
<dir>:181:2: error: too few operands for instruction
        v_bcnt_u32_b32  v0, v0

What the specification requires

The specification text this program relies on and the compiler is expected to support.

OpenMP 5.2 §13.8 p.284 l.12-13

The functionality added to the target directive is the inclusion of an executable region to be executed on a device

Evidence

Same compiler, one change

  • one change from the finding; correct on gcc_g90: AFAR 24.3.0: correct; CCE 19.0.0: correct; CCE 20.0.2: correct; CCE 21.0.3: correct; CCE 21.0.4: correct; NVHPC 25.11: correct; NVHPC 26.9: correct; GCC 16.2.0: correct
    Control program
    #include <stdio.h>
    #include <stdlib.h>
    #include <omp.h>
    
    #define M1 0x5555555555555555UL
    #define M2 0x3333333333333333UL
    #define M4 0x0f0f0f0f0f0f0f0fUL
    #define H01 0x0101010101010101UL
    
    int main(void)
    {
      const int length = 1024;
      int hf = 1, bad = 0;
      unsigned long *data = malloc(length * sizeof(unsigned long));
      int *result = malloc(length * sizeof(int));
      for (int i = 0; i < length; i++)
        data[i] = (unsigned long)(i + 1) * 0x9E3779B97F4A7C15UL;
      #pragma omp target map(from: hf)
      hf = omp_is_initial_device() ? 1 : 0;
    
      #pragma omp target map(to: data[0:length]) map(from: result[0:length])
      for (int i = 0; i < length; i++) {
        unsigned long x = data[i];
        x -= (x >> 1) & M1;
        x = (x & M2) + ((x >> 2) & M2);
        x = (x + (x >> 4)) & M4;
        x += x >> 8;
        x += x >> 16;
        x += x >> 32;
        result[i] = x & 0x7f;
      }
    
      for (int i = 0; i < length; i++) {
        int count = 0;
        for (unsigned long y = data[i]; y; count++) y &= y - 1;
        if (count != result[i]) bad++;
      }
      printf("bad= %d\n", bad);
      printf("RESULT ran_on_host=%d check=%s\n", hf, bad == 0 ? "pass" : "fail");
      free(data);
      free(result);
      return 0;
    }
    
  • one change from the finding; correct on gcc_g90: AFAR 24.3.0: correct; CCE 19.0.0: correct; CCE 20.0.2: correct; CCE 21.0.3: correct; CCE 21.0.4: correct; NVHPC 25.11: correct; NVHPC 26.9: correct; GCC 16.2.0: correct
    Control program
    #include <stdio.h>
    #include <stdlib.h>
    #include <omp.h>
    
    #define M1 0x5555555555555555UL
    #define M2 0x3333333333333333UL
    #define M4 0x0f0f0f0f0f0f0f0fUL
    #define H01 0x0101010101010101UL
    
    int main(void)
    {
      const int length = 1024;
      int hf = 1, bad = 0;
      unsigned long *data = malloc(length * sizeof(unsigned long));
      int *result = malloc(length * sizeof(int));
      for (int i = 0; i < length; i++)
        data[i] = (unsigned long)(i + 1) * 0x9E3779B97F4A7C15UL;
      #pragma omp target map(from: hf)
      hf = omp_is_initial_device() ? 1 : 0;
    
      #pragma omp target map(to: data[0:length]) map(from: result[0:length])
      for (int i = 0; i < length; i++) {
        unsigned long x = data[i];
        result[i] = __builtin_popcountll(x);
      }
    
      for (int i = 0; i < length; i++) {
        int count = 0;
        for (unsigned long y = data[i]; y; count++) y &= y - 1;
        if (count != result[i]) bad++;
      }
      printf("bad= %d\n", bad);
      printf("RESULT ran_on_host=%d check=%s\n", hf, bad == 0 ? "pass" : "fail");
      free(data);
      free(result);
      return 0;
    }
    

Other compilers run it correctly

AFAR 24.3.0, CCE 19.0.0, CCE 20.0.2, CCE 21.0.3, CCE 21.0.4, NVHPC 25.11, NVHPC 26.9