[fpc-devel] Subject: six issues from a native aarch64-win64 port (three of them not target specific)

Sven Harazim sven at syconic.org
Tue Aug 25 12:31:38 CEST 2026


Hi,

I opened six issues over the past week, each with a patch. Here is where 
they come from.

I ported mORMot 2 (~616k lines) to Windows on ARM, with a trunk compiler built
and hosted natively on the target. Once it compiled at all, its test suite ran
195 million assertions with 835 failures. Chasing those down produced four of
the issues below; the other two came afterwards, from reading the .xdata the
compiler generates for this target. My build uses clang as the assembler and
the internal linker - no ld ships for this target here, so -Xe was never
exercised.

Three of them have nothing to do with the target: 41862 needs only -OoNOREGVAR
or an inlined label to show up, 41865 is a trunk regression from four days
before my snapshot, and 41866 is as old as paszlib. They just surfaced here
first.

41859  https://gitlab.com/freepascal.org/fpc/source/-/work_items/41859
  clang emits the byte offset of the label as the addend in the instruction
  field, and when the internal linker resolves IMAGE_REL_ARM64_PAGEBASE_REL21
  it takes that addend as a page count instead of bytes. In the reproducer the
  WideString literal is then addressed 0x4000 bytes too high - the constant
  itself is emitted correctly. It comes back empty, or takes the RTL into an
  access violation once variants is linked in. This is what stopped the port on
  day one.

41862  https://gitlab.com/freepascal.org/fpc/source/-/work_items/41862
  do_consttovar (optcse) creates a tt_persistent register temp and never emits
  the matching tempdelete, so the final a_reg_sync is missing and the register
  is released after the last textual use. In the reproducer it is handed to
  another value before a loop back edge. Not target specific: it takes
  -OoNOREGVAR, or a routine that inlines something containing a label, to lose
  the regvar loop syncing that otherwise covers it.

41865  https://gitlab.com/freepascal.org/fpc/source/-/work_items/41865
  Four Currency defects on trunk since 0adf046ea0 made nf_is_currency
  load-bearing. My diagnosis is that nodes are produced or substituted without
  maintaining the flag, and do_currency_corrections then applies the 10000
  scalar where it should not, or omits it where it should. Not all four are
  that commit's own doing - the unary-minus branch it exposes dates from
  272a0e3e26 (2021) and was harmless until the flag got its present meaning.
  Two patched, two only diagnosed. No release is affected; the committer date
  on trunk is 2026-08-15.

41866  https://gitlab.com/freepascal.org/fpc/source/-/work_items/41866
  paszlib's inflate() rejects next_in=nil unconditionally; zlib only rejects it
  when avail_in<>0, and paszlib's own deflate() already has the relaxed form.
  The guard is plain Pascal with no target-dependent branch, and behaves the
  same on trunk aarch64-win64 and on 3.2.2 aarch64-linux. The patch also adds
  paszlib to TESTPACKAGESDIRS, which is what puts the attached test into the
  package test run.

41870  https://gitlab.com/freepascal.org/fpc/source/-/work_items/41870
  The epilog start index in the .xdata header is written as 1, but the field is
  a byte index into the unwind code array. With a two-byte first code, index 1
  addresses its operand byte: a 4016-byte frame gives c0 fb ..., and the
  unwinder starts the epilog scope at 0xfb - llvm-readobj calls that a bad
  opcode. For other sizes the operand byte is a valid code and is executed as
  one: 0x3f decodes as save_r19r20_x.

41871  https://gitlab.com/freepascal.org/fpc/source/-/work_items/41871
  At or above one page of locals the allocation goes through the probing path,
  where no ash_stackalloc is emitted, so it is missing from the .xdata
  altogether. A routine with no other unwind-relevant prologue operation then
  gets no .pdata entry at all - which per the specification means a leaf that
  does not touch sp and returns through lr. The defect is independent of 41870,
  but the patch depends on it landing first: it makes the allocation the first,
  multi-byte code for every such frame, which is exactly the case 41870
  mis-indexes. Measured on a compiler carrying only the 41871 patch, a
  5104-byte frame gives c1 3f, and index 1 executes the 0x3f as
  save_r19r20_x.

Why the unwind pair survived this long: for a body PC in a frame that carries
set_fp - the common case - the stack walk still comes out right, because sp is
recovered from x29 and only the non-volatile registers x19-x28 and d8-d15 are
restored from the wrong slots. It is the cases outside that which break
visibly, and they are rarer: an epilog PC (41870), or a large routine with no
.pdata entry at all (41871).

What I verified:

- a tree with all six applied builds a compiler that reaches the bootstrap
  fixpoint (make cycle: ppc2 and ppc3 are equal)
- for the 41862 and 41865 compiler patches, make allexectests
  CPU_TARGET=aarch64 OS_TARGET=win64 with and without them: identical sets of
  executed and failing tests. The baseline on this installation is bad -
  thousands of "ExecuteRedir ... Failed to execute" that look like a suite
  setup problem - so this shows no regression, not a green suite. I have not
  run that comparison for the SEH pair
- the mORMot suite with all six: 0 failures out of 195,783,438 assertions.
  It started the port at 835, but that whole drop is not the compiler patches -
  mORMot needed a handful of fixes of its own along the way, and the last four
  failures were an SQLite DLL built with SQLITE_OMIT_LOCALTIME
- the unwind behaviour with RtlVirtualUnwind and sentinel register values,
  cross-checked against llvm-readobj
- testsuite programs are attached: webtbs-format tests for all four Currency
  defects, the two unfixed ones marked %KNOWNRUNERROR, and one for paszlib

A green suite does not mean everything in the reports is fixed. What still
fails stands on its own, outside anything mORMot exercises: the two unpatched
Currency defects (div/mod, and -Oofastmath), and for 41871 two prologue shapes
the patch does not fully cover - it emits the allocation in both, but a PC at
the sub of the movz/sub pair, or inside the probe loop from five pages up,
still unwinds wrong. Closing the loop case means restructuring the probe to
leave sp alone until one final sub, the way __chkstk does.

Happy to turn any of these into merge requests if that is easier to handle, and
happy to test patches on this target - the hardware is set up here.

That is the context for the burst.

Regards,
Sven Harazim



More information about the fpc-devel mailing list