Skip to content

alpha: Add JIT support - #921

Open
mattst88 wants to merge 7 commits into
PCRE2Project:mainfrom
alphalinux-org:alpha-jit-support
Open

alpha: Add JIT support#921
mattst88 wants to merge 7 commits into
PCRE2Project:mainfrom
alphalinux-org:alpha-jit-support

Conversation

@mattst88

Copy link
Copy Markdown
Contributor

Add JIT compiler support for DEC Alpha (AXP) 64-bit, in four commits:

Base

  • Document and package the backend: add sljit Alpha sources to EXTRA_DIST, update the JIT-supported-platforms list. The deps/sljit submodule bump is deferred until the sljit backend is merged upstream.

CMPBGE scanning:

  • Implement fast_forward_char_simd, fast_requested_char_simd, and fast_forward_char_pair_simd using the Alpha CMPBGE instruction for 8-byte-at-a-time character scanning, analogous to the SIMD paths on other architectures. The first-match step uses a base-ISA binary search (NEGQ+AND to isolate the lowest set bit, then three AND+SELECT pairs), identical to glibc's Alpha strchr.

memchr:

  • For exact single-character 8-bit scanning, call glibc's memchr() instead of the inline CMPBGE loop. glibc's Alpha memchr uses CMPBGE with 3-cache-line prefetching, alignment, and software pipelining that the JIT cannot easily replicate.

Prefetching:

  • Emit PREFETCH_L1 (LDL R31) 192 bytes ahead in all three CMPBGE scanning loops. Guarded against short haystacks. Unconditional prefetch caused a ~15% regression on inputs shorter than 192 bytes.

Validated on a real HP AlphaServer ES47 (1 GHz EV7): pcre2_jit_test passes all tests in 8/16/32-bit modes; RunTest passes with and without JIT.

The last two commits could be dropped from this PR and considered separately, if desired.

Requires zherczeg/sljit#364

@zherczeg

Copy link
Copy Markdown
Collaborator

Is this architecture is still supported by HP? Alpha EV7 is released in 2003.
I want to drop mips from the codebase, because the last MIPS cpu was released in 2015.

@mattst88

Copy link
Copy Markdown
Contributor Author

Is this architecture is still supported by HP? Alpha EV7 is released in 2003.

It is not supported by HP, no.

I want to drop mips from the codebase, because the last MIPS cpu was released in 2015.

Is it causing maintenance issues? I have a passing interest in MIPS and could have a look.

Dropping it based on release date doesn't seem like the right metric.

@zherczeg

Copy link
Copy Markdown
Collaborator

The reasoning is that the jit compiler is normally used in high performance environments. If you have old machines, they are likely not used in production environments. They simply used for "showing that they still works". Supporting them is not worth the maintenance efforts. MIPS is not the first arch which was dropped, sparc and TileGX was also supported at some point of time.

Anyway, if somebody is willing to maintaining these architectures, I have no problem supporting these outdated cpus. The problem is that if the maintainer disappears after a few years. I have no resources to maintain them.

@mattst88

Copy link
Copy Markdown
Contributor Author

Thanks. That makes complete sense to me and I fully agree.

I've been working to support old/weird architectures for 15+ years now, so I'm happy to contribute to sljit and pcre2 in that capacity. (Though I cannot promise I won't suddenly be hit by a bus)

I'll start by checking on the status of the test suite on mips tomorrow. I found an issue indicating it might be more than a little broken :)

@NWilson

NWilson commented Jul 16, 2026

Copy link
Copy Markdown
Member

I don't have any particular influence over sljit. I have been struggling with personal to keep up with PCRE2 recently - so anything that keeps maintenance down would be nice.

Dead architectures like MIPS or Alpha or Sparc seem totally reasonable to drop. Retrocomputing can focus on keep old software alive on old systems, but doesn't need to bring new software to such systems. Or at least, I don't the time or energy to contribute to that effort personally.

I feel like there are several bits of code in PCRE2 (including some z/OS support and VMS) which is clearly broken, and I'm quite confident doesn't compile anymore. It's always painful to "pull the trigger" and delete it.

I'm afraid that old sljit architectures would end in the same place: code contributed by one expert, used by one or two users, and then after a couple of years be dead, and with little way for us to know if it still works.

@mattst88
mattst88 force-pushed the alpha-jit-support branch 2 times, most recently from 3175841 to a360895 Compare July 23, 2026 06:24
Pulls in the DEC Alpha 64-bit JIT backend (sljit PCRE2Project#364), which the
following commits build on, along with the other sljit changes made
since the previous update.
@mattst88
mattst88 force-pushed the alpha-jit-support branch from a360895 to 3658b9e Compare August 2, 2026 05:57
@mattst88

mattst88 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

The sljit code has landed upstream. Updated this branch to pull that in first.

@NWilson

NWilson commented Aug 8, 2026

Copy link
Copy Markdown
Member

@zherczeg I can ask Claude and GPT to review this, but otherwise... it's just a total black box to me. I know nothing about Alpha.

I presume, since you accepted the PR in sljit, that you're happy to accept the Alpha support in PCRE2?

@NWilson

NWilson commented Aug 8, 2026

Copy link
Copy Markdown
Member

We need to add the new file to the tarball manifest.

I cannot push this change, because maintainer updates to your branch are not turned on, so you'll have to do it yourself.

--- a/maint/manifest-tarball
+++ b/maint/manifest-tarball
@@ -57,6 +57,7 @@ drwxr-xr-x tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/allocator_src
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitConfigInternal.h
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitLir.c
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitLir.h
+-rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitNativeALPHA_64.c
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitNativeARM_32.c
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitNativeARM_64.c
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitNativeARM_T2_32.c

@zherczeg

zherczeg commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

While alpha support is acceptable for sljit, I am not sure about PCRE2. Even with the sljit core code, it should work. I would not go adding a lot of specialized code for PCRE2 though. @NWilson what do you think?

@NWilson

NWilson commented Aug 8, 2026

Copy link
Copy Markdown
Member

The problem is we have no easy way of reviewing or fixing any issues. I'm really not that excited about adding an architecture that's not been on sale for 20 years.

On the hand... I'm not mean, and if a user wants it, I guess I can take it.

I have asked GPT to do a very thorough review of all the new Alpha code, and it couldn't find any issues.

@mattst88 Is there the possibility of cutting down on the amount of Alpha-specific specialisation so the PCRE2 code that's specific to Alpha is shorter?

@mattst88

mattst88 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

We could certainly only land the first two commits and get basic Alpha support. I'll run some benchmarks (or find where I recorded benchmark results while writing this series) and post them.

Would it be valuable (in general) to run the PCRE2 tests under QEMU for supported platforms? That's what we do in libunwind and it has helped immensely. I'd be happy to work on that and then we can circle back to this PR once we've built some confidence (in the approach and in me).

@NWilson

NWilson commented Aug 8, 2026

Copy link
Copy Markdown
Member

We already run our CI tests on a list of processor architectures, and all OSes that we can get our hands on.

S/390X, PPC64, ARMv7, AArch64, RiscV64...

So we've even got big-endian and 32-bit platforms in there.

Unfortunately the GitHub Actions runner we use (run-on-arch-action) doesn't have Alpha. Not surprising really.

Honestly - it's over to you to keep it running. If you want to contribute a CI job for that, great. But otherwise, it's just your testing keeping it from bitrot.

Personally - I would add the absolute minimum number of lines of code to PCRE2. Why optimise for a platform with so few users?

@mattst88

mattst88 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Okay, I've opened #939 which adds QEMU testing for currently-untested platforms.

Personally - I would add the absolute minimum number of lines of code to PCRE2. Why optimise for a platform with so few users?

Understood. For me, it's a fun challenge. You're certainly under no obligation to indulge me in that. But, I do enjoy contributing upstream, so if you all are willing, I'd prefer the code to be upstream. Happy to contribute to the project outside of that goal as well, of course!

@NWilson

NWilson commented Aug 9, 2026

Copy link
Copy Markdown
Member

Very well then. I accept it! With your CI additions as well, we'll even be able to actually run the code, which is great.

And we're always grateful for contributions. I like your enthusiasm!

@NWilson

NWilson commented Aug 9, 2026

Copy link
Copy Markdown
Member

This does need a small fix though:

We need to add the new file to the tarball manifest.

I cannot push this change, because maintainer updates to your branch are not turned on, so you'll have to do it yourself.

--- a/maint/manifest-tarball
+++ b/maint/manifest-tarball
@@ -57,6 +57,7 @@ drwxr-xr-x tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/allocator_src
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitConfigInternal.h
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitLir.c
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitLir.h
+-rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitNativeALPHA_64.c
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitNativeARM_32.c
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitNativeARM_64.c
 -rw-r--r-- tarball-dir/pcre2-SNAPSHOT/deps/sljit/sljit_src/sljitNativeARM_T2_32.c

@mattst88

mattst88 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Okay, here's the story with the follow-on commits. They're pretty important. I made a benchmark that checks a bunch of different cases (which I'll make a PR for, in case it appears to be generally useful).

  • ff-char-absent, ff-lit-absent, ff-range-absent: the first code unit never
    occurs, so the scan runs the whole buffer with no false starts. This is pure
    fast-forward cost.
  • ff-class-dense: dense first character, every candidate fails immediately.
    Worst case for a vectorized scan.
  • ff-lit-found, ff-caseless-*, ff-pair-common, re-email, re-alt,
    re-longword: mixed and realistic.
  • ctl-anchored, ctl-startline: no fast-forward, used as a noise floor.

This is the table of operations in the benchmark:

static const struct benchmark benchmarks[] = {
  /* Pure fast-forward cost: the first code unit never occurs, so the JIT
   * scans the whole buffer and reports NOMATCH without a single false start. */
  { "ff-char-absent",      "Q",                    "single absent char" },
  { "ff-lit-absent",       "zqjx",                 "absent literal, char pair start" },
  { "ff-lit-found",        "wombat",               "rare literal, present" },

  /* Character-range fast-forward. */
  { "ff-range-absent",     "[XYZ]",                "absent range" },
  { "ff-range-digits",     "[0-9]{6}",             "sparse digit run" },
  { "ff-class-dense",      "[aeiou]qzx",           "dense first char, all false starts" },

  /* Caseless and pair-based fast-forward. */
  { "ff-caseless-absent",  "(?i)zqjx",             "caseless absent literal" },
  { "ff-caseless-found",   "(?i)Hello",            "caseless literal, present" },
  { "ff-pair-common",      "th",                   "very common char pair" },

  /* Realistic mixed patterns. */
  { "re-email",            "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}", "email address" },
  { "re-longword",         "\\b\\w{12,}\\b",       "long word boundary scan" },
  { "re-alt",              "(?:wombat|numbat|quokka)", "alternation of literals" },

  /* Controls that do not use fast-forward at all; these should be flat across
   * every commit in the series and act as a noise floor. */
  { "ctl-anchored",        "\\A\\w+",              "anchored, no fast-forward" },
  { "ctl-startline",       "(?m)^the\\b",          "multiline start-of-line" }
};

Here are the numbers, per commit. These are throughput in MB/s:

bench base (no JIT) sljit +cmpbge +prefetch +memchr overall
ff-char-absent 1255.6 139.8 436.2 866.7 1225.2 0.98x
ff-lit-absent 1254.2 184.5 312.9 683.5 680.0 0.54x
ff-lit-found 55.5 199.1 278.2 516.5 527.2 9.50x
ff-range-absent 100.7 134.4 134.8 134.2 133.0 1.32x
ff-range-digits 99.8 134.0 134.5 133.8 132.5 1.33x
ff-class-dense 7.1 32.3 34.2 34.3 33.5 4.71x
ff-caseless-absent 629.4 198.7 318.2 617.1 614.6 0.98x
ff-caseless-found 0.5 168.3 191.9 267.9 262.6 570.89x (suspect)
ff-pair-common 19.6 66.2 114.1 133.3 119.6 6.11x
re-email 2.7 39.2 41.4 41.1 40.9 15.26x
re-longword 3.0 10.9 10.9 10.9 10.9 3.62x
re-alt 13.3 173.7 313.5 561.6 560.0 42.04x
ctl-startline 80.2 95.4 205.0 289.0 294.1 3.67x
ctl-anchored 1339751.5 2856440.1 2910311.8 2651481.6 2542647.1 1.90x
commit pcre2 lines
pre-series base — old sljit pin, no Alpha backend → interpreter only
sljit bump: Alpha JIT works, no pcre2 SIMD 0
docs/packaging only (codegen identical to above) 3
CMPBGE fast-forward scanning +513
guarded prefetch in CMPBGE loops +24
memchr for exact single-char scan +76

9a3be4af (CMPBGE, 513 lines) gives 37% to 112% on single-character, pair, caseless and alternation fast-forward, and on multiline start-of-line. It does nothing for character ranges (-1.4%) and little for the dense false-start case (+5%).

b1842a38 (prefetch, 24 lines) gives a further 17% to 119% on every pattern CMPBGE helped. It is by a wide margin the best return per line in the series.

3658b9ec (memchr, 76 lines) gives 41.8% on exactly one shape, the exact single character scan, and nothing measurable elsewhere. It costs 2.1% on ff-class-dense. Its value is that it closes the last deficit against the interpreter on that shape, from 0.70x to 0.98x.

List the new sljit Alpha backend sources in EXTRA_DIST and add DEC Alpha
64-bit to the platforms supported by the JIT compiler in the documentation.

The corresponding deps/sljit submodule bump is deferred until the backend
is merged upstream; validated under qemu-alpha (pcre2_jit_test passes in
8/16/32-bit modes and RunTest passes with and without JIT).
Use the Alpha CMPBGE instruction for 8-byte-at-a-time character
scanning in the JIT compiler, similar to how other architectures use
SIMD instructions. CMPBGE compares 8 byte pairs and produces an 8-bit
mask, which combined with XOR gives us byte-equality matching across a
whole quadword at once.

Implement fast_forward_char_simd, fast_requested_char_simd, and
fast_forward_char_pair_simd. The first-match step uses a base-ISA
binary search (NEGQ+AND to isolate the lowest set bit, then three
AND+SELECT pairs), identical to glibc's Alpha strchr; no CIX required.

For 16/32-bit code units, a post-processing step compacts the per-byte
CMPBGE mask into a per-code-unit mask by ANDing adjacent bits.

The char-pair function handles Alpha's 8-byte alignment requirement for
LDQ by constructing the unaligned second data word from two aligned
loads using SRL/SHL/OR.
Emit PREFETCH_L1 (LDL R31) 192 bytes ahead of the current position
in all three fast-forward scanning loops: fast_forward_char_simd,
fast_requested_char_simd, and fast_forward_char_pair_simd.

The 192-byte (3 cache line) prefetch distance matches what glibc's
Alpha memchr uses. Without this, the JIT's CMPBGE loop loses to the
interpreter's memchr() call on full-scan no-match patterns because
memchr has prefetching and software pipelining.

The prefetch is guarded by a comparison against STR_END - 192 to
avoid reading past the end of the buffer on short haystacks, where
unconditional prefetching caused a ~15% regression.
For 8-bit mode with an exact single-character match, call glibc's
memchr() instead of the inline CMPBGE scanning loop. glibc's Alpha
memchr uses CMPBGE with 3-cache-line-ahead prefetching, cacheline
alignment, and software pipelining (overlapping the next load with
the current compare to hide L1 latency).

The CMPBGE loop is retained for case-insensitive (match1i) and
two-character (match2) scanning where memchr does not apply.

Both fast_forward_char_simd and fast_requested_char_simd gain the
memchr path; fast_forward_char_pair_simd is unchanged since it
searches for two characters at different offsets simultaneously.
We have hand-written code for every architecture SLJIT supports, but CI
only exercises a few of them. The existing "ptarmigan" job covers s390x,
ppc64le, armv7, aarch64 and riscv64, which leaves the 32-bit ARM, PowerPC,
MIPS and LoongArch backends untested. The ARM one is easy to miss:
Ubuntu's armhf compiler defaults to Thumb-2, so armv7 exercises
sljitNativeARM_T2_32.c and never sljitNativeARM_32.c.

Add a job which cross-compiles for each of them and runs the test suite
under qemu-user. This is a different mechanism to ptarmigan, which boots
a container of the target architecture: run-on-arch-action supports only
armv6, armv7, aarch64, riscv64, s390x and ppc64le, so none of these
targets can be added to it. Cross-compiling is also faster, since only
the test programs are emulated rather than the compiler as well.

RunTest already accepts a "-sim" prefix for exactly this purpose, so the
test scripts need no changes. Shared libraries are disabled so that the
test programs are real executables rather than libtool wrapper scripts,
which qemu-user cannot run.

Both MIPS release 6 and the earlier releases are covered, since
SLJIT_MIPS_REV selects different code for each.
Add alpha to the coelacanth matrix, so the new sljitNativeALPHA_64.c
backend is exercised by CI like the others.

Alpha is not a release architecture, but the cross toolchain and the
emulator are both packaged: gcc-14-alpha-linux-gnu and qemu-alpha are
available in Ubuntu 26.04, and no extra apt sources are needed. The
libc package is named libc6.1-dev-alpha-cross, because Alpha uses
libc6.1 rather than libc6, and it provides the libc6-dev-alpha-cross
name that the job's "libc" matrix field builds, so the existing install
line needs no change.

Verified by cross-compiling with gcc 15 and running the full suite under
qemu-alpha: RunTest passes with and without JIT, and pcre2_jit_test and
pcre2posix_test both pass, in 8-, 16- and 32-bit modes.
@mattst88
mattst88 force-pushed the alpha-jit-support branch from 3658b9e to 98814f9 Compare August 9, 2026 19:19
@mattst88

mattst88 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

This does need a small fix though:

Thank you! I've fixed that and repushed. Note that the PR currently now contains the patch from #939 so that I could add another patch that adds CI support for alpha on top. I expect the other PR to land before this and rebase out of this PR.

@NWilson

NWilson commented Aug 9, 2026

Copy link
Copy Markdown
Member

Wow. This is the dedication of a true hobbyist (I presume). You don't work with Alpha commercially, do you? Anyway, those performance stats are awesome.

We'll take it!

@mattst88

Copy link
Copy Markdown
Contributor Author

Thank you!

Yep, true hobbyist. Just got interested in weird old computers a long time ago and never grew out of it. (I've recently begun restoring the content of the AlphaLinux.org wiki which I originally created in 2008 and was lost when a hard drive failed in ~2011 -- see https://alphalinux.org/wiki/). If this lands, it'll be a news item for the dozens of Alpha users.

Dozens!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants