← board

i386: the C entry stub hands main argc and argv in the wrong slots

Repro

#include <stdio.h>
int main(int argc, char **argv) {
  int i; printf("argc=%d\n", argc);
  for (i = 0; i < argc; i++) printf("[%s]\n", argv[i]);
  return 0;
}
$ pinned --target=i386 argv.c argv_i386 && tools/run_target.sh i386 ./argv_i386 hello world
argc=-4958108                 <- a STACK ADDRESS read as an int; the loop then runs 0 times

$ pinned --target=aarch64 ... && tools/run_target.sh aarch64 ./argv_a64 hello world
argc=3
[/…/argv_a64]
[hello]
[world]

Not a regression from the v256 pin. Checked against the previous pinned binary (ca4dac8d0, before the entry stub gained its finalizer call) — same garbage, so the finalizer change is exonerated and this is long-standing.

Pascal on i386 is fine: the same argv through ParamCount/ParamStr prints count=2 and all three strings. The Pascal entry path does not go through this stub, which is why nothing caught it.

Mechanism (measured, not inferred)

The stub pushes cdecl order — argv first, then argc, leaving argc at the lowest address (cparser.inc:8421):

lea ecx, [eax+4]   ; argv
push ecx           ; arg2
push [eax]         ; arg1 = argc
call main

But ir_codegen386.inc:3140 states the internal convention outright: "The internal convention pushes leftmost-first", and the reversal that would make a call cdecl-shaped is explicitly disabled inside a C program:

vaFwd := (procIdx >= 0) and (ProcVariadic[procIdx] or (ProcCdecl[procIdx] and not CProgramMode));

So main, compiled in CProgramMode, reads arg0 from the higher slot — which holds argv. That is exactly what the observed value is: argc=-4958108 is 0xFFB4…, a stack address, not a count.

Fix

Swap the two pushes in the TARGET_I386 arm so the stub matches the convention the callee actually uses (leftmost-first: push argc, then argv). Worth a look at the other four arms while there — arm32/aarch64/riscv32 are verified correct by the test above, x86-64 too, so i386 is the odd one out.

Gate

tools/gcc_diff_probe.sh --target i386 with an argv case, plus test/crtl_atexit.c's e/x/n sub-modes (they select on argv[1], so they are an argv assertion on every target and currently all collapse to the default branch on i386 — a passing i386 run of those three is the proof).

Log