Skip to content

HTTPREXX: REXX Server Pages on rexx370 — Phase 1#2

Merged
mgrossmann merged 11 commits into
mainfrom
feature/httprexx-rexx370-phase1
Jul 2, 2026
Merged

HTTPREXX: REXX Server Pages on rexx370 — Phase 1#2
mgrossmann merged 11 commits into
mainfrom
feature/httprexx-rexx370-phase1

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Fixes #1.

Rebuilds HTTPREXX on rexx370, implementing Phase 1 of the REXX Server Pages
spec. Green-field rewrite (the brexx370 sources are removed).

Stage 1 — mbt v2 migration

  • project.toml to the v2 schema: type=application, one HTTPREXX
    [[module]], httpd + ufsd dependencies; the v1 [mvs.build.datasets] /
    [[link.module]] blocks, HTTPSAY, and the crent370/BREXX linkage are dropped.
  • mbt submodule bumped to the v2 line. Minimal skeleton builds and links.

Stage 2 — Phase 1

  • .rxp transpiler (src/rxptrans.c): per-source-line say coalescing with
    ||, quote-doubling escaper, one newline swallowed after a statement ?>,
    blank lines preserved, mid-line statement split. Pure C; table-driven host
    unit tests (make test-host) — 12/12, incl. the spec's hello.rxp byte-exact.
  • Execution glue (src/httprexx.c): reads the page from UFS (libufs),
    transpiles .rxp, then per request: IRXINIT a fresh LPE via __linkds,
    overwrite IRXEXTE.io_routine with a heap-free httprexx_io (the MODNAMET
    override is a no-op in rexx370; the SAY opcode reads the pointer by
    indirection, so the post-init patch is sufficient), IRXEXEC the in-storage
    INSTBLK (ENVBLOCK in P9, query string as an ARGTABLE for PARSE ARG), IRXTERM
    via a small C-callable asm shim (asm/htrxterm.asm, R0 linkage). The body is
    emitted on the text path (http_printf) so httpd translates EBCDIC→ASCII.
  • The IRX services are reached at runtime against the installed load modules —
    rexx370 is not a build dependency; only its struct layouts are reproduced
    locally in include/irxbind.h (no rexx370 change; honors spec §2).

Docs

  • doc/rexx370-bindings.md — the confirmed IRX call sequences and the flagged
    Phase-1 deferrals (POST/PULL → EOF, pool variables → IRXEXCOM later).
  • doc/phase1-smoketest.md + examples/hello.{rexx,rxp} — the manual
    end-to-end check (MOD= config, requests, expected render).

Verification

  • Builds clean: cc370 (-Wall -Werror) + as370 + ld370.
  • Transpiler host unit tests: 12/12.
  • End-to-end render on Hercules is the manual smoke test in
    doc/phase1-smoketest.md (not run here). First-run watch item: the IRX
    modules must be installed/loadable by the httpd address space.

Replace the brexx370 module build (HTTPSAY + HTTPREXX via [[link.module]],
crent370 dependency) with the mbt v2 schema: type=application, a single
HTTPREXX [[module]] (startup=crt1), and httpd + ufsd dependencies. The v1
[mvs.build.datasets] and [[link.module]] blocks and the BREXX linkage are
dropped; the mbt submodule is bumped to the v2 line.

The rexx370 IRX services (IRXINIT/IRXEXEC/IRXTERM) are resolved at runtime
against the modules already installed on the system, so rexx370 is not a
build dependency -- only its headers are vendored later under include/.

src/httprexx.c is a minimal CGI skeleton: the httpd/httpc handshake, the
per-connection UFS session, and the extension-router shape. It builds and
links cleanly under cc370. The brexx370 sources (cgistart.c, httprexx.c,
httpsay.c) are removed in favor of the rexx370 rewrite.
rxptrans.c transforms a .rxp template into a single REXX program: each
source line that produces output becomes one `say` built with `||`
concatenation, while `<?rexx ... ?>` statement tags pass through verbatim
and `<?rexx= e ?>` expressions are emitted as `(e)`. Literals are escaped by
REXX quote-doubling. The newline immediately after a statement tag's `?>` is
swallowed so a statement-only line emits no stray `say ''`; blank literal
lines are preserved; a mid-line statement tag splits the line into two SAYs.

This is the pure-C path that feeds the REXX source to IRXEXEC (spec section
5). test/tstrxp.c is a table-driven host unit test (make test-host) covering
the spec's hello.rxp byte-exact, the mid-line statement edge case, quote
doubling, blank-line preservation, and the error path. 12/12 assertions pass.
httprexx.c now drives the installed rexx370 IRX services per request to run a
.rexx/.rxp page and return its SAY output:

  - reads the source from UFS (libufs) by SCRIPT_FILENAME, transpiling .rxp;
  - IRXINIT a fresh LPE via __linkds (7-slot CALL,VL list);
  - overwrites IRXEXTE.io_routine with httprexx_io (the MODNAMET override is a
    no-op in rexx370; the SAY opcode reads the pointer by indirection, so a
    post-init patch redirects every SAY) and binds the request context through
    ENVBLOCK userfield;
  - IRXEXEC the in-storage INSTBLK (10-slot CALL,VL list; the ENVBLOCK is passed
    in P9, the query string as an ARGTABLE for PARSE ARG, SUBROUTINE call type);
  - IRXTERM via a small C-callable asm shim (asm/htrxterm.asm) because IRXTERM
    takes the ENVBLOCK in R0, which __linkds cannot set;
  - flushes the buffered page to httpc (default text/html), or a clean 500.

httprexx_io is heap-free: it memcpy's SAY output into a fixed buffer allocated
in the request context (it runs nested in IRXEXEC's call context). irxbind.h is
a minimal local view of the IRX control blocks (no wholesale header vendoring),
with a compile-time INSTBLK size check. doc/rexx370-bindings.md records the
confirmed call sequences and the flagged Phase-1 deviations.

Builds clean: cc370 (-Wall -Werror) + as370 + ld370.
doc/phase1-smoketest.md walks the manual end-to-end check (deploy, MOD=
routing, upload, curl) with the exact expected render for examples/hello.rexx
and examples/hello.rxp, plus the wtof diagnostics and the flagged Phase-1
deferrals. The samples include the spec's hello.rxp.
…_send

http_send is httpd's raw vector path; the rendered page is text and must go
through the EBCDIC->ASCII translation that http_printf performs (the same path
httplua uses for its body). Sending raw EBCDIC would garble the response on the
wire. The output buffer now reserves a trailing NUL for the "%s" emit.
The STM line's inline comment ran to column 77. Column 72 is the as370/HLASM
continuation column, so a non-blank there made the assembler read the next
line (BALR R12,0) as a continuation of the STM operand -- silently dropping
the BALR that establishes the base register. HRXTERM then ran with a garbage
base, read garbage literals, and issued GETMAIN/LOAD with garbage operands,
corrupting the caller frame and abending S0C1 on return into run_rexx.

Rewrite htrxterm.asm with disciplined columns (short inline comments, nothing
past column 71). Verified against the as370 listing: BALR now assembles (05C0)
immediately after the STM.
The as370 LOAD macro emits the "return-code requested" SVC 8 form (R1 =
X'80000000'), which MVS 3.8j does not honor: LOAD EP=IRXTERM came back with
entry point 0, so HRXTERM branched to R15=0 (low core) and abended S0C1.

Obtain the IRXTERM entry point in C via __load() -- the crent370/libc370 loader
rexx370 itself uses -- and balance it with __delete(). The asm shim shrinks to
HRXCALL, which only sets R0=env and BALRs the supplied entry over a GETMAIN'd
save area (no LOAD/DELETE macros). Defensive: a NULL entry logs and skips
(the LPE is not freed, but the request still renders) instead of branching to 0.
- send_page now translates the rendered body EBCDIC(IBM-1047)->ASCII with
  the 1047 table and sends it raw, matching httpd's static-file path. The
  server default codepage (CP037) mangled the variant characters [ ] { } # @ |.
- dispatch strips CR bytes so CRLF-terminated .rxp/.rexx sources (uploaded via
  FTP or MVSMF) tokenize as clean newline-only text.
- IRXTERM is temporarily compiled out: terminating an LPE after an IRXEXEC run
  faults inside IRXTERM (pending a rexx370 fix); the LPE is leaked per request
  for now. The __load/HRXCALL path is kept for a one-line re-enable.
- Removed the temporary debug WTO/hex-dump instrumentation; kept the real
  HTTPREXX: error logs.
The "S0C1 after IRXTERM" crash that forced IRXTERM to be disabled was
never in rexx370 - it was HRXCALL's own epilog:

    LM    R0,R12,20(,R13)

LM is RS-format (D(B) operand syntax); as370 silently assembles the
RX-style D(,B) spelling with BASE=0, so the epilog reloaded R0-R12
from PSA low core 0x14-0x48 (old PSWs, CVT pointer) instead of the
caller's save area. The adjacent RX-format L R14,12(,R13) was correct,
which is why R13/R14 always looked sane while the base registers came
back as garbage - wild branches with rotating abend codes that
imitated storage corruption downstream.

Write the operand as 20(R13) and re-enable the per-request IRXTERM
teardown (no more leaked LPEs). Root-cause analysis: rexx370
docs/irxterm-c-host-crash.md; assembler issue: mvslovers/cc370#12;
rexx370 side: mvslovers/rexx370#205.
@mgrossmann

Copy link
Copy Markdown
Contributor Author

80e742d re-enables the per-request IRXTERM teardown: the "S0C1 after IRXTERM" that forced the disable was root-caused to HRXCALL's own epilog — LM R0,R12,20(,R13) is RS-format and as370 silently assembles the D(,B) spelling with BASE=0, restoring R0-R12 from PSA low core. Full analysis in rexx370 docs/irxterm-c-host-crash.md; see also mvslovers/rexx370#205 and mvslovers/cc370#12. No more leaked LPEs.

…load failure

Three focused fixes to the CGI handler, none of which changes the render path:

- send_page now emits Content-Length. The whole page is buffered, so the exact
  byte count is known; sending it (as httpd's static path does) is deterministic
  and preserves keep-alive without relying on httpd's fallback that injects
  Transfer-Encoding: chunked when no length is present. The EBCDIC->ASCII xlate
  is 1:1, so the count is stable.

- read_ufs sizes its buffer to the file via ufs_stat (capped at HRX_SRC_MAX)
  instead of always mallocing a fixed 64 KB per request, which matters on the
  memory-constrained target. Falls back to the full cap if stat is unavailable.

- run_rexx logs when __load(IRXTERM) fails. Without IRXTERM the IRXINIT LPE
  cannot be torn down, so a persistent worker would leak that storage per
  request; the failure is now visible instead of silent.

Measurement context: demo.rxp latency (TTFB ~0.42s) is dominated by REXX
generation, not delivery (transfer ~8 ms), so these are correctness/memory
fixes, not the performance lever.
Wire httprexx into the shared mvslovers/mbt reusable workflows, matching the
other ecosystem repos (httpd et al.): build on every pull_request and on
push to main, release on v* tags. httprexx had no .github/ at all, so PRs
were merged ungated; this closes that gap.
@mgrossmann
mgrossmann merged commit ebd975e into main Jul 2, 2026
1 check passed
@mgrossmann
mgrossmann deleted the feature/httprexx-rexx370-phase1 branch July 2, 2026 20:01
mgrossmann added a commit that referenced this pull request Jul 3, 2026
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.

HTTPREXX: REXX Server Pages on rexx370 — Phase 1

1 participant