From b34eb208b32857f84379689b3a78a91b4cfd33ec Mon Sep 17 00:00:00 2001 From: alice Date: Thu, 13 Aug 2026 05:04:26 +0000 Subject: [PATCH 1/2] =?UTF-8?q?vms-ec70:=20MMK.EXE=20native=20=E2=80=94=20?= =?UTF-8?q?MadGoat=20MMK=20parses=20descrip.mms=20+=20drives=20the=20toolc?= =?UTF-8?q?hain=20(self-host=20spine=20#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build the vendored MadGoat MMK (tests/corpus/tier3-mmk/) as an OVMX-native image. MMK now REALLY parses a descrip.mms through the just-landed self-host stack and emits the correct build plan: - CLI$ compiled-CLD: mmk_cld.cld is compiled at run time by cli$compile_cld (vms-8c1) and driven by cli$dcl_parse/present/get_value (ovmx_mmk_cld.c). - description-file parse: MMK's parse_descrip/parse_objects run the REAL lib$table_parse engine, driven by the vms-486 PARSE_TABLES.MAR->C grammar compiled -DOVMX_MMK_PRODUCTION so its transitions fire MMK's own parse_store / parse_obj_store (not the vms-486 test probe). - build engine: MMK builds the target/dependency graph and, in /NOACTION, emits the resolved TCC/LINK commands in dependency order. The 15 vendored make-engine TUs are stock except for tagged "OVMX (vms-ec70)" seams (grep the tag). A clean-room (Rule 8) build shim in tests/corpus/tier3-mmk/ovmx/ adapts the stock source to the OVMX RTL: - ovmx_mmk_compat.h VMS storage-class keywords; struct/constant spellings; a PP_NARG call-site macro replacing DEC C's va_count (no SysV x86-64 ABI equivalent); variadic wrappers for RTL arity differences (sys$parse/search/filescan, ots$cvt_tu_l, str$position, lib$get[_symbol]/set_logical, lib$create_vm_zone). - ovmx_mmk_{cld,sp,cms,builtins,compat}.c companions. Real RTL fixes landed alongside (all VMS-faithful, benefit any caller): - tpadef.h/lib_tparse.c: add TPA$B_CHAR (matched char) — MMK needs it to accrete suffix rules. - lib_vm.c: honour LIB$M_VM_GET_FILL0 (zero returned blocks) — MMK's zones rely on it; add lib$reset_vm_zone (was missing). - lib_output.c: lib$get_foreign now supports a dynamic (class D) descriptor (str$copy_dx) — MMK passes an INIT_DYNDESC, which previously returned empty. - rms/nam.h: add nam$b_nop/nam$l_rlf/nam$t_dvi + NAM$M_SYNCHK. - lib_vm.c create_vm_zone over-read hardened via the MMK-side wrapper. Test (CI-wired): tests/toolchain/run_mmk_parse.sh + a CMake mmk_native target build MMK.EXE against the OVMX RTL and run it on a real descrip.mms; the test asserts MMK emits `TCC` then `LINK` in dependency order (exit = SS$_NORMAL). Passes in the standard ctest (toolchain-mmk-parse). Full suite: 167/167 green, no regressions; vms-486 grammar test still green. Native LINK.EXE/IMGACT packaging recipe: src/vmslink/mk_mmk.sh (mk_dcl.sh-style). DEFERRED (flagged, not faked): actual command EXECUTION (turning the plan into a built+activated image) rides MMK's DCL-subprocess/mailbox/AST drive (build_target.c send_cmd_and_wait), which needs OVMX lib$spawn-of-DCL + mailboxes + write-attention ASTs; ovmx_mmk_sp.c supports /NOACTION honestly and returns an authentic error for real builds (no fake success). Also deferred: .OLB module-date (lbr$) in get_rdt, CMS, and the compiled default-rules codegen tools (genstruc/mmk_compile_rules) — MMK ships no built-in rules yet, so a descrip.mms must state rules explicitly. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/libvms/include/lib$routines.h | 10 + src/libvms/include/tpadef.h | 3 + src/libvms/rtl/lib_output.c | 25 ++- src/libvms/rtl/lib_tparse.c | 3 + src/libvms/rtl/lib_vm.c | 54 ++++++ src/vmslink/mk_mmk.sh | 118 ++++++++++++ src/vmsrms/include/rms/nam.h | 11 ++ tests/corpus/tier3-mmk/default_rules.c | 5 + tests/corpus/tier3-mmk/get_rdt.c | 17 ++ tests/corpus/tier3-mmk/misc.c | 11 ++ tests/corpus/tier3-mmk/mmk.c | 11 +- tests/corpus/tier3-mmk/mmk.h | 10 + tests/corpus/tier3-mmk/ovmx/builtins.h | 4 + tests/corpus/tier3-mmk/ovmx/mhddef.h | 4 + .../corpus/tier3-mmk/ovmx/mmk_default_rules.h | 35 ++++ .../corpus/tier3-mmk/ovmx/ovmx_mmk_builtins.c | 71 ++++++++ tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cld.c | 44 +++++ tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cms.c | 39 ++++ tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.c | 105 +++++++++++ tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.h | 172 ++++++++++++++++++ tests/corpus/tier3-mmk/ovmx/ovmx_mmk_sp.c | 80 ++++++++ tests/corpus/tier3-mmk/ovmx/strdef.h | 4 + tests/corpus/tier3-mmk/parse_descrip.c | 19 +- tests/corpus/tier3-mmk/parse_objects.c | 14 +- tests/corpus/tier3-mmk/symbols.c | 11 ++ tests/libvms/mmk_parse_tables.c | 15 ++ tests/toolchain/CMakeLists.txt | 73 ++++++++ tests/toolchain/run_mmk_parse.sh | 90 +++++++++ 28 files changed, 1053 insertions(+), 5 deletions(-) create mode 100755 src/vmslink/mk_mmk.sh create mode 100644 tests/corpus/tier3-mmk/ovmx/builtins.h create mode 100644 tests/corpus/tier3-mmk/ovmx/mhddef.h create mode 100644 tests/corpus/tier3-mmk/ovmx/mmk_default_rules.h create mode 100644 tests/corpus/tier3-mmk/ovmx/ovmx_mmk_builtins.c create mode 100644 tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cld.c create mode 100644 tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cms.c create mode 100644 tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.c create mode 100644 tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.h create mode 100644 tests/corpus/tier3-mmk/ovmx/ovmx_mmk_sp.c create mode 100644 tests/corpus/tier3-mmk/ovmx/strdef.h create mode 100755 tests/toolchain/run_mmk_parse.sh diff --git a/src/libvms/include/lib$routines.h b/src/libvms/include/lib$routines.h index d75e43225..9aced62ab 100644 --- a/src/libvms/include/lib$routines.h +++ b/src/libvms/include/lib$routines.h @@ -138,6 +138,16 @@ uint32_t lib$delete_vm_zone( const uint32_t *zone_id ); +/* + * lib$reset_vm_zone - Bulk-free all memory in a zone, keeping the zone active. + * + * @param zone_id Pointer to zone identifier to reset + * @return SS$_NORMAL, or LIB$_BADZONE for an invalid/inactive zone + */ +uint32_t lib$reset_vm_zone( + const uint32_t *zone_id +); + /* ================================================================ * Terminal I/O Routines * ================================================================ */ diff --git a/src/libvms/include/tpadef.h b/src/libvms/include/tpadef.h index d2085aa83..c8dcedc5b 100644 --- a/src/libvms/include/tpadef.h +++ b/src/libvms/include/tpadef.h @@ -38,6 +38,9 @@ struct _tpadef { char *tpa$l_tokenptr; /* Pointer to start of most recent token */ uint32_t tpa$l_number; /* Numeric value of most recent numeric token */ uint32_t tpa$l_param; /* User parameter (passed from state table) */ + uint8_t tpa$b_char; /* First character of the most recent token match + * (VMS TPARSE exposes the matched character here; + * MMK reads it to build suffix rules char-by-char). */ }; typedef struct _tpadef TPADEF; diff --git a/src/libvms/rtl/lib_output.c b/src/libvms/rtl/lib_output.c index 02605afbf..52337ba7b 100644 --- a/src/libvms/rtl/lib_output.c +++ b/src/libvms/rtl/lib_output.c @@ -13,6 +13,7 @@ #include "rmsdef.h" #include "descrip.h" #include "lib$routines.h" +#include "str$routines.h" /* * lib$put_output - Write descriptor contents to stdout with a newline. @@ -175,7 +176,12 @@ uint32_t lib$get_foreign(struct dsc$descriptor_s *result, const struct dsc$descriptor_s *prompt, uint16_t *result_len, ...) { - if (!result || !result->dsc$a_pointer) return SS$_BADPARAM; + /* A dynamic (class D) descriptor legitimately starts with a NULL pointer + * and zero length — str$copy_dx allocates it below. Only a static + * descriptor must already point at a buffer. */ + if (!result) return SS$_BADPARAM; + if (result->dsc$b_class != DSC$K_CLASS_D && !result->dsc$a_pointer) + return SS$_BADPARAM; /* First call with a foreign command line present: return the raw tail * DCL published, then consume it so a second call reads SYS$INPUT. An @@ -184,6 +190,23 @@ uint32_t lib$get_foreign(struct dsc$descriptor_s *result, if (tail && tail[0] != '\0') { size_t len = strlen(tail); + /* Dynamic (class D) result descriptor: allocate/resize to hold the whole + * tail via str$copy_dx, matching VMS LIB$GET_FOREIGN, which accepts a + * dynamic string descriptor and returns the full command line. (Callers + * such as MMK pass an INIT_DYNDESC descriptor whose dsc$w_length starts + * at 0; the static path below would otherwise copy zero bytes.) */ + if (result->dsc$b_class == DSC$K_CLASS_D) { + struct dsc$descriptor_s src; + src.dsc$w_length = (uint16_t)len; + src.dsc$b_dtype = DSC$K_DTYPE_T; + src.dsc$b_class = DSC$K_CLASS_S; + src.dsc$a_pointer = (char *)tail; + uint32_t st = str$copy_dx(result, &src); + if (result_len) *result_len = (uint16_t)len; + unsetenv("VMS_FOREIGN_CMD"); + return (st & 1) ? SS$_NORMAL : st; + } + uint16_t copylen = (uint16_t)len; int truncated = 0; if (len > result->dsc$w_length) { diff --git a/src/libvms/rtl/lib_tparse.c b/src/libvms/rtl/lib_tparse.c index d6baaee88..b75ecda58 100644 --- a/src/libvms/rtl/lib_tparse.c +++ b/src/libvms/rtl/lib_tparse.c @@ -95,6 +95,9 @@ static void tpa_take(tpa_ctx *ctx, char *start, uint32_t n) { TPADEF *t = ctx->tpa; t->tpa$l_tokenptr = start; t->tpa$l_tokencnt = n; + /* Expose the first matched character (VMS TPA$B_CHAR); MMK reads it after a + * single-character (TPA$_ANY / literal) match to accrete suffix strings. */ + if (n >= 1) t->tpa$b_char = (uint8_t)start[0]; t->tpa$l_stringptr = start + n; /* stringcnt was measured from `start`; subtract what we consumed. */ t->tpa$l_stringcnt = t->tpa$l_stringcnt - n; diff --git a/src/libvms/rtl/lib_vm.c b/src/libvms/rtl/lib_vm.c index efc507025..ac6fdaeb4 100644 --- a/src/libvms/rtl/lib_vm.c +++ b/src/libvms/rtl/lib_vm.c @@ -294,6 +294,12 @@ uint32_t lib$get_vm(const uint32_t *num_bytes, void **base_adr, ...) hdr->zone_id = zid; *base_adr = (char *)raw + sizeof(struct alloc_hdr); + /* Zero the returned block. VMS callers routinely create zones with + * LIB$M_VM_GET_FILL0 (e.g. MMK's symbol/rule/dependency zones) and rely on + * fresh allocations being zeroed — a struct whose pointer fields must read + * as NULL until set. Lookaside reuse returns dirty memory, so zero here to + * honour the FILL0 contract. (vms-ec70) */ + memset(*base_adr, 0, hdr->size); return SS$_NORMAL; } @@ -473,6 +479,54 @@ uint32_t lib$delete_vm_zone(const uint32_t *zone_id) return SS$_NORMAL; } +/* + * lib$reset_vm_zone - Bulk-free all memory in a zone but keep the zone ALIVE. + * + * Like lib$delete_vm_zone, all extents are munmap'd in one shot; unlike delete, + * the zone slot stays active and is re-initialised empty so the caller can keep + * allocating from it. (VMS callers use this to recycle a scratch zone between + * passes — e.g. MMK's pattern-substitution leaf zone.) Added for vms-ec70. + */ +uint32_t lib$reset_vm_zone(const uint32_t *zone_id) +{ + if (!zone_id) + return SS$_BADPARAM; + + uint32_t zid = *zone_id; + + ensure_init(); + + pthread_mutex_lock(&zone_table_lock); + + if (zid >= MAX_ZONES || !zones[zid].active) { + pthread_mutex_unlock(&zone_table_lock); + return LIB$_BADZONE; + } + + struct vm_zone *zone = &zones[zid]; + + pthread_mutex_lock(&zone->lock); + + struct extent *ext = zone->extents; + while (ext) { + struct extent *next = ext->next; + munmap((void *)ext, ext->size); + ext = next; + } + zone->extents = NULL; + zone->bump_ptr = NULL; + zone->bump_remain = 0; + for (int i = 0; i < LOOKASIDE_CLASSES; i++) + zone->lookaside[i] = NULL; + zone->total_alloc = 0; + zone->total_free = 0; + + pthread_mutex_unlock(&zone->lock); + pthread_mutex_unlock(&zone_table_lock); + + return SS$_NORMAL; +} + /* * lib$get_vm_page - Allocate memory in VMS page units (512 bytes each). */ diff --git a/src/vmslink/mk_mmk.sh b/src/vmslink/mk_mmk.sh new file mode 100755 index 000000000..f4995ed46 --- /dev/null +++ b/src/vmslink/mk_mmk.sh @@ -0,0 +1,118 @@ +#!/bin/sh +# mk_mmk.sh — build recipe for MMK.EXE, the MadGoat MMK ("make" for VMS) built +# AS a VMS-native EXECUTABLE image (bead vms-ec70, self-host spine #4). Mirrors +# mk_dcl.sh / mk_tcc.sh exactly: compile every TU with the proven freestanding- +# musl CFLAGS, then LINK.EXE --executable --use {DECC$SHR + the six OVMX +# shareables} -o MMK.EXE, activated by IMGACT.EXE. +# +# COMPOSITION (see tests/toolchain/CMakeLists.txt for the identical source list +# used by the host functional ctest toolchain-mmk-parse): +# - 15 vendored MadGoat make-engine TUs (tests/corpus/tier3-mmk/), stock except +# for tagged "OVMX (vms-ec70)" seams — grep the tag to see every edit; +# - the vms-486 PARSE_TABLES.MAR -> C grammar (tests/libvms/mmk_parse_tables.c), +# compiled -DOVMX_MMK_PRODUCTION so its transitions fire MMK's real +# parse_store / parse_obj_store (not the vms-486 test probe); +# - the OVMX companions (tests/corpus/tier3-mmk/ovmx/): +# ovmx_mmk_compat.c RTL call-arity forwarding wrappers, +# ovmx_mmk_cld.c compiles mmk_cld.cld at run time via cli$compile_cld, +# ovmx_mmk_sp.c subprocess boundary (NOACTION dry-run; real exec is +# the deferred DCL-subprocess drive — see the header), +# ovmx_mmk_cms.c honest CMS-not-available stubs, +# ovmx_mmk_builtins.c _INSQUE/_REMQUE + sp_once/lib$find_image_symbol. +# +# The force-included ovmx_mmk_compat.h adapts the stock vendored source to the +# OVMX RTL (VMS storage-class keywords, struct/constant spellings, the va_count +# call-site counting macro, the RTL arity wrappers). See its header for the full +# rationale; every OVMX-defined representation is labeled a design choice (Rule 8). +# +# link.c / imgact.c are the complete toolchain and are OUT of file-domain here — +# do NOT edit them. The RTL entry points MMK imports (lib$table_parse, +# cli$compile_cld/cli$dcl_parse/cli$present/cli$get_value, lib$reset_vm_zone, +# sys$filescan, ...) are auto-exported in the producer shareables' symbol vectors +# (mk_*_shr.sh generate the vector from the objects' global symbols). +# +# Usage: mk_mmk.sh \ +# \ +# \ +# [repo-src-dir] +# Env: CC (default gcc), CFLAGS (default aarch64 musl; x86_64 caller sets its own) +# Must run in the musl container where the producer .EXE already exist. +set -e + +LINK_EXE=${1:?usage: mk_mmk.sh [repo-src]} +OUT=${2:?need output MMK.EXE path} +DECC_SHR=${3:?need DECC\$SHR.EXE} +VMS_SHR=${4:?need LIBVMS\$SHR.EXE} +PROC_SHR=${5:?need LIBVMSPROCESS\$SHR.EXE} +FS_SHR=${6:?need LIBVMSFS\$SHR.EXE} +LNM_SHR=${7:?need LIBVMSLNM\$SHR.EXE} +RMS_SHR=${8:?need LIBVMSRMS\$SHR.EXE} +SYS_SHR=${9:?need LIBVMSSYS\$SHR.EXE} +HERE=$(cd "$(dirname "$0")" && pwd) # src/vmslink +REPO_SRC=${10:-$(cd "$HERE/.." && pwd)} # src +REPO=$(cd "$REPO_SRC/.." && pwd) # repo root +CORPUS="$REPO/tests/corpus/tier3-mmk" +OVMX="$CORPUS/ovmx" +GRAMMAR="$REPO/tests/libvms/mmk_parse_tables.c" +CC=${CC:-gcc} + +for f in "$DECC_SHR" "$VMS_SHR" "$PROC_SHR" "$FS_SHR" "$LNM_SHR" "$RMS_SHR" "$SYS_SHR"; do + [ -f "$f" ] || { echo "mk_mmk: producer image not found: $f"; exit 1; } +done +[ -d "$CORPUS" ] || { echo "mk_mmk: MMK corpus not found: $CORPUS"; exit 1; } + +WORK=${WORK:-/tmp/mk-mmk} +rm -rf "$WORK"; mkdir -p "$WORK" + +# Embed mmk_cld.cld as a C string (mmk_cld_src.h), the same header the host +# ctest generates via gen_mmk_cld_src.cmake. +{ + echo "static const char mmk_cld_source[] =" + sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/ "/' -e 's/$/\\n"/' "$CORPUS/mmk_cld.cld" + echo ";" +} > "$WORK/mmk_cld_src.h" + +CFLAGS="${CFLAGS:--fPIC -O2 -ffreestanding -fno-builtin -fno-stack-protector -mno-outline-atomics -U_FORTIFY_SOURCE}" +DEFS="-D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE -D__CRTL_VER=80400000 -DOVMX_MMK" +INCS="-I$WORK -I$OVMX -I$CORPUS -I$REPO/tests/libvms \ +-I$REPO_SRC/libvms/include -I$REPO_SRC/vmsrms/include -I$REPO_SRC/vmsfs/include \ +-I$REPO_SRC/vmslnm/include -I$REPO_SRC/vmsprocess/include" +FINC="-include $OVMX/ovmx_mmk_compat.h" + +CORPUS_TUS="mmk parse_descrip parse_objects build_target misc mem fileio symbols \ +objects default_rules get_rdt readdesc str" +COMPANIONS="ovmx_mmk_cld ovmx_mmk_sp ovmx_mmk_cms ovmx_mmk_builtins" + +echo "mk_mmk: CC=$CC LINK.EXE=$LINK_EXE" +OBJS="" +for t in $CORPUS_TUS; do + echo " cc $t.c" + $CC $CFLAGS $DEFS $FINC $INCS -c -o "$WORK/$t.o" "$CORPUS/$t.c" + OBJS="$OBJS $WORK/$t.o" +done +echo " cc mmk_parse_tables.c (OVMX_MMK_PRODUCTION — real parse_store)" +$CC $CFLAGS $DEFS -DOVMX_MMK_PRODUCTION $FINC $INCS -c -o "$WORK/mmk_parse_tables.o" "$GRAMMAR" +OBJS="$OBJS $WORK/mmk_parse_tables.o" +for t in $COMPANIONS; do + echo " cc $t.c" + $CC $CFLAGS $DEFS $FINC $INCS -c -o "$WORK/$t.o" "$OVMX/$t.c" + OBJS="$OBJS $WORK/$t.o" +done +# ovmx_mmk_compat.c carries #undefs for the wrapper macros, so it is safe to +# force-include the compat header uniformly with every other TU. +echo " cc ovmx_mmk_compat.c (RTL arity wrappers)" +$CC $CFLAGS $DEFS $FINC $INCS -c -o "$WORK/ovmx_mmk_compat.o" "$OVMX/ovmx_mmk_compat.c" +OBJS="$OBJS $WORK/ovmx_mmk_compat.o" + +NOBJ=$(echo $OBJS | wc -w) +echo "mk_mmk: $NOBJ objects compiled (13 corpus + grammar + 5 companions = 19 expected)" +[ "$NOBJ" -eq 19 ] || { echo "mk_mmk: FAIL: expected 19 objects, got $NOBJ"; exit 1; } + +echo "mk_mmk: LINK.EXE --executable --use {7 producers} -> $OUT" +# shellcheck disable=SC2086 +"$LINK_EXE" --executable \ + --use "$DECC_SHR" --use "$VMS_SHR" --use "$PROC_SHR" \ + --use "$FS_SHR" --use "$LNM_SHR" --use "$RMS_SHR" --use "$SYS_SHR" \ + -o "$OUT" $OBJS + +echo "mk_mmk: created $OUT" diff --git a/src/vmsrms/include/rms/nam.h b/src/vmsrms/include/rms/nam.h index b83d7cc91..ebbeff421 100644 --- a/src/vmsrms/include/rms/nam.h +++ b/src/vmsrms/include/rms/nam.h @@ -45,6 +45,13 @@ struct NAM { uint32_t nam$l_wcc; /* Wildcard context (internal) */ /* Internal */ void *nam$$l_context; /* Wildcard search context (internal) */ + /* Parse-control / related-file / device-id fields (vms-ec70: needed by + * callers such as MadGoat MMK that set NAM$M_SYNCHK for syntax-only + * $PARSE, chain a related-file NAM, or read the device-id back). Appended + * at the end so all pre-existing field offsets are unchanged. */ + uint8_t nam$b_nop; /* $PARSE options (NAM$M_SYNCHK etc.) */ + struct NAM *nam$l_rlf; /* Related-file NAM for relative $PARSE */ + char nam$t_dvi[16]; /* Device-id (counted string) after $PARSE */ }; /* NAM flags (nam$l_fnb) */ @@ -69,4 +76,8 @@ struct NAM { .nam$b_bln = sizeof(struct NAM) \ } +/* nam$b_nop $PARSE option flags */ +#define NAM$M_SYNCHK 0x08 /* Syntax-only parse (no device/dir check) */ +#define NAM$M_PWD 0x10 /* Parse-with-directory (search list) */ + #endif /* __RMS_NAM_H */ diff --git a/tests/corpus/tier3-mmk/default_rules.c b/tests/corpus/tier3-mmk/default_rules.c index 6bd0f3704..f3eb327f8 100644 --- a/tests/corpus/tier3-mmk/default_rules.c +++ b/tests/corpus/tier3-mmk/default_rules.c @@ -57,7 +57,12 @@ #pragma module DEFAULT_RULES "V1.3" #include "mmk.h" #include "globals.h" +#ifdef OVMX_MMK /* OVMX (vms-ec70): generated at build time by + * mmk_compile_rules into WORK, found via -I$WORK */ +#include "mmk_default_rules.h" +#else #include "etc_dir:mmk_default_rules.h" +#endif /* **++ diff --git a/tests/corpus/tier3-mmk/get_rdt.c b/tests/corpus/tier3-mmk/get_rdt.c index 3c5b77e9f..66b8944c1 100755 --- a/tests/corpus/tier3-mmk/get_rdt.c +++ b/tests/corpus/tier3-mmk/get_rdt.c @@ -63,8 +63,12 @@ #pragma module GET_RDT "V1.4" #include "mmk.h" #include +#ifndef OVMX_MMK /* OVMX (vms-ec70): the LBR (.OLB module-date) path is + * seamed out below — OVMX has no lbr$ callable RTL. + * See the OVMX stubs at the bottom of this #ifndef. */ #include #include +#endif /* ** Forward declarations @@ -72,6 +76,8 @@ unsigned int lbr_get_rdt(char *, char *, TIME *); void lbr_flush(void); +#ifndef OVMX_MMK + /* ** Context structure and header for the list that tracks them */ @@ -283,6 +289,17 @@ void lbr_flush (void) { } lbrcount = 0; } +#else /* OVMX_MMK — honest stubs: .OLB module revision-date lookup is not wired + * (deferred; see vms-ec70 PR). Returns an even (failure) status so a + * library-module target is treated as needing a rebuild rather than + * silently reporting a bogus date. */ +unsigned int lbr_get_rdt (char *lib, char *mod, TIME *rdt) { + (void)lib; (void)mod; + if (rdt) memset(rdt, 0, sizeof(*rdt)); + return RMS$_FNF; /* module not found -> forces "stale" */ +} +void lbr_flush (void) { } +#endif /* OVMX_MMK */ /* **++ diff --git a/tests/corpus/tier3-mmk/misc.c b/tests/corpus/tier3-mmk/misc.c index b2385dc83..62ef6d5bc 100644 --- a/tests/corpus/tier3-mmk/misc.c +++ b/tests/corpus/tier3-mmk/misc.c @@ -95,7 +95,9 @@ */ void Build_Suffix_List(char *, int); char *itoa(int); +#ifndef OVMX_MMK /* OVMX (vms-ec70): cat() is the counted ovmx_cat form (mmk.h) */ char *cat(char *, ...); +#endif char *trim(char *); char *find_char(char *, char *, char *); void upcase(char *); @@ -255,7 +257,12 @@ char *itoa(int i) { ** **-- */ +#ifdef OVMX_MMK /* OVMX (vms-ec70): counted form; the call-site macro (mmk.h) + * supplies ovmx_ac = total arg count in place of va_count. */ +char *ovmx_cat(int ovmx_ac, char *in, ...) { +#else char *cat(char *in, ...) { +#endif int actualcount; va_list ap; @@ -263,7 +270,11 @@ char *cat(char *in, ...) { int i, inlen, len, outlen; outlen = inlen = (in == (char *)0) ? 0 : strlen(in); +#ifdef OVMX_MMK + actualcount = ovmx_ac; +#else va_count(actualcount); +#endif va_start(ap, in); i = 1; while (i < actualcount) { diff --git a/tests/corpus/tier3-mmk/mmk.c b/tests/corpus/tier3-mmk/mmk.c index e06df7c62..2e5c6b956 100644 --- a/tests/corpus/tier3-mmk/mmk.c +++ b/tests/corpus/tier3-mmk/mmk.c @@ -240,12 +240,19 @@ /* ** External references */ -#ifndef __VAX +#ifdef OVMX_MMK +/* OVMX (vms-ec70): MMK_CLD is the table compiled at run time from mmk_cld.cld + * by cli$compile_cld (see ovmx_mmk_cld.c), not a SET COMMAND object symbol. */ +struct cli_command_table; +struct cli_command_table *ovmx_mmk_get_cld(void); +#define MMK_CLD (ovmx_mmk_get_cld()) +#elif !defined(__VAX) extern mmk_cld; +#define MMK_CLD (&mmk_cld) #else globalref mmk_cld; -#endif #define MMK_CLD (&mmk_cld) +#endif void Map_Default_Rules(void); diff --git a/tests/corpus/tier3-mmk/mmk.h b/tests/corpus/tier3-mmk/mmk.h index a745a3105..60b6d70c6 100644 --- a/tests/corpus/tier3-mmk/mmk.h +++ b/tests/corpus/tier3-mmk/mmk.h @@ -377,7 +377,12 @@ typedef POINTER SPHANDLE; ** SYMBOLS */ struct SYMBOL * Lookup_Symbol(char *name); +#ifdef OVMX_MMK /* OVMX (vms-ec70): counted form (va_count has no x86-64 ABI equivalent) */ + void ovmx_Define_Symbol(int ovmx_ac, SYMTYPE symtype, char *name, char *val, int vallen, ...); +#define Define_Symbol(...) ovmx_Define_Symbol(OVMX_NARG(__VA_ARGS__), __VA_ARGS__) +#else void Define_Symbol(SYMTYPE symtype, char *name, char *val, int vallen, ...); +#endif int Resolve_Symbols(char *in, int inlen, char * *out, int *outlen, int dont_resolve_unknowns); void Clear_Local_Symbols(void); void Create_Local_Symbols(struct DEPEND *dep, struct OBJREF *srcref, struct QUE *chgque); @@ -405,7 +410,12 @@ typedef POINTER SPHANDLE; */ void Build_Suffix_List(char *line, int linelen); char * itoa(int); +#ifdef OVMX_MMK /* OVMX (vms-ec70): counted form (va_count has no x86-64 ABI equivalent) */ + char * ovmx_cat(int ovmx_ac, char *dest, ...); +#define cat(...) ovmx_cat(OVMX_NARG(__VA_ARGS__), __VA_ARGS__) +#else char * cat(char *dest, ...); +#endif char * trim(char *s); char * find_char(char *base, char *end, char *charset); void upcase(char *str); diff --git a/tests/corpus/tier3-mmk/ovmx/builtins.h b/tests/corpus/tier3-mmk/ovmx/builtins.h new file mode 100644 index 000000000..c263cb21f --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/builtins.h @@ -0,0 +1,4 @@ +/* OVMX (vms-ec70): empty shim for VMS — MMK includes it but uses nothing from it on the OVMX build. */ +#ifndef OVMX_SHIM_BUILTINS_H +#define OVMX_SHIM_BUILTINS_H +#endif diff --git a/tests/corpus/tier3-mmk/ovmx/mhddef.h b/tests/corpus/tier3-mmk/ovmx/mhddef.h new file mode 100644 index 000000000..65c1b058d --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/mhddef.h @@ -0,0 +1,4 @@ +/* OVMX (vms-ec70): empty shim for VMS — MMK includes it but uses nothing from it on the OVMX build. */ +#ifndef OVMX_SHIM_MHDDEF_H +#define OVMX_SHIM_MHDDEF_H +#endif diff --git a/tests/corpus/tier3-mmk/ovmx/mmk_default_rules.h b/tests/corpus/tier3-mmk/ovmx/mmk_default_rules.h new file mode 100644 index 000000000..439559b26 --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/mmk_default_rules.h @@ -0,0 +1,35 @@ +/* + * mmk_default_rules.h - OVMX (vms-ec70) minimal compiled default-rules table. + * + * *** OVMX DESIGN CHOICE (clean-room, Rule 8) *** + * + * On OpenVMS this header is GENERATED by MMK's own GENSTRUC / MMK_COMPILE_RULES + * build tools from mmk_default_rules_.mms (the built-in .SUFFIXES and + * ".c.obj:" style implicit rules, whose command bodies are DCL "CC"/"LINK" + * lines). Those tools are themselves MMK-toolchain images with their own CLDs; + * bringing them up on OVMX is deferred (see the vms-ec70 PR). Until then MMK.EXE + * ships with NO compiled-in default rules: a description file must state its + * rules/commands explicitly (which the self-host spine's descrip.mms trees do). + * + * Map_Default_Rules() (default_rules.c) guards every block with *_INIT_COUNT > 0, + * so zero counts make it install an empty suffix/symbol/rule set cleanly. + */ +#ifndef OVMX_MMK_DEFAULT_RULES_H +#define OVMX_MMK_DEFAULT_RULES_H + +/* [1]-sized (ISO C forbids zero-length arrays); the counts below are 0 so the + * single element is never touched. */ +static struct SFX sfx_init[1]; +#define SFX_INIT_COUNT 0 + +static struct SYMBOL gsym_init[1]; +#define GSYM_INIT_COUNT 0 + +static struct RULE rule_init[1]; +#define RULE_INIT_COUNT 0 +#define RULE_INIT_COUNT_REAL 0 +#define RULE_REAL_LAST_IDX 0 + +static struct RULE default_rule_init; /* .cmdque.flags == 0 -> no commands */ + +#endif /* OVMX_MMK_DEFAULT_RULES_H */ diff --git a/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_builtins.c b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_builtins.c new file mode 100644 index 000000000..2118d414b --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_builtins.c @@ -0,0 +1,71 @@ +/* + * ovmx_mmk_builtins.c - OVMX (vms-ec70) implementations of the VMS/DEC-C + * built-ins and RTL entry points MMK references that the + * OVMX toolchain/RTL does not provide as intrinsics. + * + * *** OVMX DESIGN CHOICE (clean-room, Rule 8) *** + * + * _INSQUE / _REMQUE - DEC C generates these for the VAX/x86 INSQUE/REMQUE + * instructions (self-relative doubly-linked queue insert + * / remove). MMK's queue macros (mmk.h queue_insert / + * queue_remove) call them on the non-Alpha path. The + * semantics are public (OpenVMS MACRO-32 / architecture + * manuals); implemented here on the standard {flink,blink} + * entry layout every MMK queue structure begins with. + * + * sp_once - "run one command, collect its output" (parse_descrip.c + * uses it for command-substitution during parsing). This + * needs the same DCL-subprocess drive that build_target's + * command execution needs (see ovmx_mmk_sp.c); it is NOT + * yet wired, so it returns an honest failure rather than + * faking output. A description file that uses command + * substitution therefore fails cleanly (deferred gap). + * + * lib$find_image_symbol - dynamic image symbol lookup; MMK uses it only for an + * optional dynamically-loaded feature, off the spine path. + * Honest "not found" so the optional feature is disabled, + * never faked. + */ +#include +#include + +#ifndef SS$_UNSUPPORTED +#define SS$_UNSUPPORTED 0x00000924 +#endif +#ifndef LIB$_KEYNOTFOU +#define LIB$_KEYNOTFOU 0x00158244 /* even (failure): key not found */ +#endif + +struct ovmx_q { struct ovmx_q *flink, *blink; }; + +/* _INSQUE(entry, pred): insert `entry` into the queue immediately after `pred`. */ +void _INSQUE(void *entry, void *pred) +{ + struct ovmx_q *e = (struct ovmx_q *)entry; + struct ovmx_q *p = (struct ovmx_q *)pred; + e->flink = p->flink; + e->blink = p; + p->flink->blink = e; + p->flink = e; +} + +/* _REMQUE(entry, addr): unlink `entry` from its queue; store its address at *addr. */ +void _REMQUE(void *entry, void *addr) +{ + struct ovmx_q *e = (struct ovmx_q *)entry; + e->blink->flink = e->flink; + e->flink->blink = e->blink; + if (addr) *(void **)addr = entry; +} + +unsigned int sp_once(void *cmd, void *actrtn, void *result) +{ + (void)cmd; (void)actrtn; (void)result; + return SS$_UNSUPPORTED; /* command-substitution drive not wired (deferred) */ +} + +unsigned int lib$find_image_symbol(void *image, void *symbol, void *symval, ...) +{ + (void)image; (void)symbol; (void)symval; + return LIB$_KEYNOTFOU; +} diff --git a/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cld.c b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cld.c new file mode 100644 index 000000000..c304724dd --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cld.c @@ -0,0 +1,44 @@ +/* + * ovmx_mmk_cld.c - OVMX (vms-ec70) compiled-CLD provider for MMK. + * + * *** OVMX DESIGN CHOICE (clean-room, Rule 8) *** + * + * On OpenVMS, MMK's command table (MMK_CLD) is produced by SET COMMAND compiling + * mmk_cld.cld into an object linked into the image; mmk.c references it as an + * external symbol `mmk_cld`. OVMX instead compiles the SAME mmk_cld.cld SOURCE + * at run time with cli$compile_cld (the OVMX CLI$ callable interface, bead + * vms-8c1), and hands the resulting table to cli$dcl_parse. mmk.c's OVMX seam + * routes MMK_CLD -> ovmx_mmk_get_cld(). The CLD grammar itself is unchanged + * vendored MadGoat source. + * + * The CLD source text is embedded via mmk_cld_src.h, generated at build time + * from tests/corpus/tier3-mmk/mmk_cld.cld by mk_mmk.sh (see that recipe). + */ +#include +#include +#include +#include +#include + +#include "mmk_cld_src.h" /* static const char mmk_cld_source[]; */ + +/* Returns the compiled MMK command table, compiling it once on first use. + * On failure returns NULL — cli$dcl_parse then returns SS$_BADPARAM, which + * mmk.c reports honestly (no silent success). */ +struct cli_command_table *ovmx_mmk_get_cld(void) +{ + static struct cli_command_table *cached = NULL; + if (cached) return cached; + + struct dsc$descriptor_s src; + src.dsc$w_length = (uint16_t)(sizeof(mmk_cld_source) - 1); + src.dsc$b_dtype = DSC$K_DTYPE_T; + src.dsc$b_class = DSC$K_CLASS_S; + src.dsc$a_pointer = (char *)mmk_cld_source; + + struct cli_command_table *tbl = NULL; + uint32_t st = cli$compile_cld(&src, &tbl); + if ((st & 1) == 0) return NULL; + cached = tbl; + return cached; +} diff --git a/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cms.c b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cms.c new file mode 100644 index 000000000..a7cd2218b --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_cms.c @@ -0,0 +1,39 @@ +/* + * ovmx_mmk_cms.c - OVMX (vms-ec70) honest CMS stubs for MMK. + * + * *** OVMX DESIGN CHOICE (clean-room, Rule 8) + DEFERRED-GAP FLAG *** + * + * MMK optionally integrates with DEC/VMS CMS (Code Management System) — the + * vendored cms_interface.c uses the CMS$ callable library, which OVMX does not + * provide. CMS is off by default (the /CMS qualifier and the use_cms global + * gate every call), and it is not part of the self-host spine. These stubs + * replace cms_interface.c and return an honest failure (CMS not available) so + * MMK never silently pretends a CMS operation succeeded; if a description file + * ever asks for CMS, MMK reports it cannot do it rather than faking it. + */ +#include +#include + +#ifndef SS$_UNSUPPORTED +#define SS$_UNSUPPORTED 0x00000924 /* even (failure) */ +#endif + +unsigned int cms_get_rdt(char *fspec, char *generation, void *rdt) +{ + (void)fspec; (void)generation; (void)rdt; + return SS$_UNSUPPORTED; +} + +unsigned int cms_fetch_file(char *fspec, char *outspec) +{ + (void)fspec; (void)outspec; + return SS$_UNSUPPORTED; +} + +unsigned int cms_parse_name(char *fspec, char *libname, int libsize, int *llen, + char *elename, int elesize, int *elen, int flags) +{ + (void)fspec; (void)libname; (void)libsize; (void)llen; + (void)elename; (void)elesize; (void)elen; (void)flags; + return SS$_UNSUPPORTED; +} diff --git a/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.c b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.c new file mode 100644 index 000000000..08682a02c --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.c @@ -0,0 +1,105 @@ +/* + * ovmx_mmk_compat.c - OVMX (vms-ec70) RTL call-arity forwarding wrappers for + * the vendored MadGoat MMK (self-host spine #4). + * + * *** OVMX DESIGN CHOICE (clean-room, Rule 8) *** + * + * MMK calls several VMS routines omitting optional trailing arguments (AST + * addresses, item lists) that OVMX declares with fixed prototypes. The + * force-included ovmx_mmk_compat.h maps each such name to one of the variadic + * wrappers below; every wrapper simply supplies the omitted arguments and + * forwards to the REAL OVMX routine. No OVMX routine is weakened. + * + * This file may be compiled WITH the force-included ovmx_mmk_compat.h (uniform + * build flags); the #undef block below drops the wrapper macros so the bodies + * here call the REAL OVMX routines instead of recursing into themselves. + */ +#include +#include + +/* Drop the call-site wrapper macros so this TU sees the real OVMX routines. */ +#undef sys$parse +#undef sys$search +#undef sys$filescan +#undef lib$getdvi +#undef ots$cvt_tu_l +#undef str$position +#undef lib$get_symbol +#undef lib$set_logical +#undef lib$create_vm_zone +#include +#include +#include +#include +#include +#include +#include +#include + +/* $PARSE / $SEARCH: MMK passes (fab) or (fab,0,0); OVMX takes (fab,err,suc). */ +uint32_t ovmx_mmk_sys_parse(void *fab, ...) { return sys$parse(fab, 0, 0); } +uint32_t ovmx_mmk_sys_search(void *fab, ...) { return sys$search(fab, 0, 0); } + +/* $FILESCAN: MMK passes (src,list,flags) or (src,list,flags,0,0); OVMX takes 3. */ +uint32_t ovmx_mmk_sys_filescan(const void *srcstr, void *valuelst, void *fldflags, ...) +{ + return sys$filescan((const struct dsc$descriptor_s *)srcstr, + (ILE2 *)valuelst, (uint32_t *)fldflags); +} + +/* LIB$GETDVI: MMK's older form (item, &chan, devnam, &result); OVMX's full form + * is (item, chan-by-value, devnam, resultval, resultstring, string_length). + * MMK uses this only in its terminal CTRL-T code (misc.c), off the spine path; + * forward best-effort with the string outputs omitted. */ +uint32_t ovmx_mmk_lib_getdvi(const void *item_code, void *chan, void *devnam, + void *result, ...) +{ + uint16_t ch = chan ? *(uint16_t *)chan : 0; + return lib$getdvi((const uint32_t *)item_code, ch, + (const struct dsc$descriptor_s *)devnam, result, 0, 0); +} + +/* OTS$CVT_TU_L: MMK passes (src,dest); OVMX takes (src,dest,size,flags). */ +uint32_t ovmx_mmk_ots_cvt_tu_l(const void *src, void *dest, ...) +{ + return ots$cvt_tu_l((const struct dsc$descriptor_s *)src, + (uint32_t *)dest, sizeof(uint32_t), 0); +} + +/* STR$POSITION: MMK passes (src,sub) or (src,sub,&start); OVMX takes 3. */ +uint32_t ovmx_mmk_str_position(const void *src, const void *sub, ...) +{ + va_list ap; const uint32_t *start = 0; + va_start(ap, sub); start = va_arg(ap, const uint32_t *); va_end(ap); + return str$position((const struct dsc$descriptor_s *)src, + (const struct dsc$descriptor_s *)sub, start); +} + +/* LIB$GET_SYMBOL: MMK passes (sym,val); OVMX takes (sym,val,vallen,tabtype). */ +uint32_t ovmx_mmk_lib_get_symbol(const void *sym, void *val, ...) +{ + return lib$get_symbol((const struct dsc$descriptor_s *)sym, + (struct dsc$descriptor_s *)val, 0, 0); +} + +/* LIB$CREATE_VM_ZONE: MMK passes (zone_id, &algorithm, &blocksize, &flags) — + * 3 optional args. OVMX's lib$create_vm_zone unconditionally va_arg's TEN + * optional arguments (to reach the position-10 zone-name), which reads PAST the + * caller's actual argument list on the SysV x86-64 ABI (there is no VMS + * argument-count register) and then dereferences garbage as a name descriptor. + * Forward with an explicit, fully-populated NULL argument list so the name slot + * is a real NULL — no over-read, no name. OVMX zones are quick-fit and ignore + * the algorithm/block-size hints, so dropping them is functionally identical. */ +uint32_t ovmx_mmk_create_vm_zone(uint32_t *zone_id, ...) +{ + return lib$create_vm_zone(zone_id, + (void*)0,(void*)0,(void*)0,(void*)0,(void*)0, + (void*)0,(void*)0,(void*)0,(void*)0,(void*)0); +} + +/* LIB$SET_LOGICAL: MMK passes (lognam,eqvnam); OVMX takes 5. */ +uint32_t ovmx_mmk_lib_set_logical(const void *lognam, const void *eqvnam, ...) +{ + return lib$set_logical((const struct dsc$descriptor_s *)lognam, + (const struct dsc$descriptor_s *)eqvnam, 0, 0, 0); +} diff --git a/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.h b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.h new file mode 100644 index 000000000..366a381d3 --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_compat.h @@ -0,0 +1,172 @@ +/* + * ovmx_mmk_compat.h - OVMX build-compatibility shim for the vendored MadGoat + * MMK (self-host spine #4, bead vms-ec70). + * + * *** OVMX DESIGN CHOICE (clean-room, Rule 8) *** + * + * MadGoat MMK is third-party BSD freeware (tests/corpus/tier3-mmk/, NOT + * VSI/HPE/DEC source). It was written for DEC C on OpenVMS and uses a handful + * of DEC-C storage-class keywords and older RTL call *arities* that OVMX's C + * headers do not spell the same way. This header is force-included (cc + * -include) into every MMK translation unit so the *stock vendored source* + * compiles against the OVMX RTL unmodified, EXCEPT for a small number of edits + * that are tagged "OVMX (vms-ec70)" directly in the vendored files (grep for + * that tag to see every one). + * + * The three things this header does: + * 1. Neutralise the DEC-C storage-class keywords (globaldef/globalref/ + * readonly/noshare/variant_*) that ISO C does not have. + * 2. Reconcile struct/constant spellings MMK expects with OVMX's headers + * (struct tpadef -> struct _tpadef; TPA$C_LENGTH0; the NAM$M_/IO$M_/DVI$_/ + * JPI$_ constants MMK's non-load-bearing terminal/CTRL-T code names; the + * XABFHC/XABRDT record-attribute XABs — MMK-local, OVMX RMS ignores the + * XAB chain, so these carry MMK's expected fields only). + * 3. Adapt the RTL call *arity* differences. VMS system services take + * optional trailing arguments (AST addresses, item lists) that MMK omits; + * OVMX declares them with fixed prototypes. We include the real OVMX + * prototypes FIRST (locking their include guards), then map each affected + * name to a variadic forwarding wrapper (ovmx_mmk_*, defined in + * ovmx_mmk_compat.c) that supplies the omitted arguments and calls the + * real OVMX routine. No OVMX header or RTL implementation is weakened. + */ +#ifndef OVMX_MMK_COMPAT_H +#define OVMX_MMK_COMPAT_H + +/* --- 1. Pull in the real OVMX RTL headers FIRST, so their include guards are + * set and their real prototypes are parsed BEFORE any wrapper macro + * below can rename an identifier. When MMK's own sources later + * #include these, the guards make them no-ops. -------------------- */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* --- 2a. DEC-C storage classes (ISO C has no equivalent; these are the + * placement/linkage hints DEC C accepts). globaldef/globalref map to + * ordinary external linkage; readonly to const; the rest vanish. --- */ +#define globaldef +#define globalref extern +#define readonly const +#define noshare +#define variant_struct struct +#define variant_union union +/* MMK's vendored clidefs.h re-declares the CLI$_ status codes with DEC-C + * `globalvalue`, which OVMX already provides as macros in . + * Suppress the vendored header by pre-defining its include guard; the CLI$ + * callable prototypes come from (included above). */ +#define clidefs_h__ + +/* --- 2b. struct-name reconciliation: MMK writes `struct tpadef`; OVMX's + * spells the tag `struct _tpadef` (typedef TPADEF). Only + * MMK's own use sites are affected (all OVMX headers are already + * included above). ------------------------------------------------ */ +#define tpadef _tpadef + +/* --- 2c. Constants MMK names that OVMX's headers do not (yet) define. + * Values transcribed from the public $TPADEF/$IODEF/$DVIDEF/$JPIDEF + * definitions (clean-room, Rule 8). The IO$M_/DVI$_/JPI$_ ones are + * used only by MMK's terminal CTRL-T attention code (misc.c), which is + * not on the parse/build path this spine exercises. --------------- */ +#ifndef TPA$C_LENGTH0 +#define TPA$C_LENGTH0 ((int)sizeof(struct _tpadef)) /* base TPARSE block bytes */ +#endif +#ifndef TPA$C_LENGTHN +#define TPA$C_LENGTHN ((int)sizeof(struct _tpadef)) +#endif +#ifndef IO$M_WRTATTN +#define IO$M_WRTATTN 0x0100 /* $QIO func modifier: write-attention AST */ +#endif +#ifndef IO$M_READATTN +#define IO$M_READATTN 0x0200 /* $QIO func modifier: read-attention AST */ +#endif +#ifndef IO$M_OUTBAND +#define IO$M_OUTBAND 0x0080 /* $QIO func modifier: out-of-band AST */ +#endif +#ifndef DVI$_TRM +#define DVI$_TRM 16 /* $GETDVI: is-a-terminal (boolean) */ +#endif +#ifndef DVI$_DEVBUFSIZ +#define DVI$_DEVBUFSIZ 19 /* $GETDVI: device buffer size */ +#endif +#ifndef JPI$_DIOCNT +#define JPI$_DIOCNT 0x040c /* $GETJPI: direct-I/O count */ +#endif +#ifndef JPI$_BIOCNT +#define JPI$_BIOCNT 0x040b /* $GETJPI: buffered-I/O count */ +#endif + +/* --- 2d. XABFHC / XABRDT record-attribute XABs. MMK chains these off its FAB + * to read a file's longest-record-length (xab$w_lrl) and revision + * date/time (xab$q_rdt). OVMX RMS does not consume the XAB chain, so + * these are MMK-local structures carrying exactly the fields MMK + * touches; the fields stay zero (MMK's own `== 0 ? default` fallbacks + * then apply — a big read buffer, and an RDT of 0 == "always stale", + * which is correct for a from-scratch build). See the deferred-gap + * note in the PR: precise LRL/RDT would need OVMX RMS XAB support. -- */ +struct XABFHC { uint16_t xab$w_lrl; uint32_t xab$l_ebk; uint16_t xab$w_ffb; }; +struct XABRDT { uint8_t xab$q_rdt[8]; uint16_t xab$w_rvn; }; +#define cc$rms_xabfhc ((struct XABFHC){0}) +#define cc$rms_xabrdt ((struct XABRDT){{0}}) +#ifndef cc$rms_xabpro +/* (cc$rms_xabpro exists in OVMX rms.h if MMK ever needs it; guard only.) */ +#endif + +/* --- 3. RTL call-arity adapters. Each variadic wrapper is defined in + * ovmx_mmk_compat.c (which does NOT force-include this header, so the + * names below resolve to the real OVMX routines there). Function-like + * macros expand only at MMK's call sites; the real prototypes were + * already parsed above. ---------------------------------------------- */ +uint32_t ovmx_mmk_sys_parse (void *fab, ...); +uint32_t ovmx_mmk_sys_search (void *fab, ...); +uint32_t ovmx_mmk_sys_filescan(const void *srcstr, void *valuelst, void *fldflags, ...); +uint32_t ovmx_mmk_lib_getdvi (const void *item_code, void *chan, void *devnam, void *result, ...); +uint32_t ovmx_mmk_ots_cvt_tu_l(const void *src, void *dest, ...); +uint32_t ovmx_mmk_str_position(const void *src, const void *sub, ...); +uint32_t ovmx_mmk_lib_get_symbol(const void *sym, void *val, ...); +uint32_t ovmx_mmk_lib_set_logical(const void *lognam, const void *eqvnam, ...); +uint32_t ovmx_mmk_create_vm_zone(uint32_t *zone_id, ...); + +#define sys$parse(...) ovmx_mmk_sys_parse(__VA_ARGS__) +#define sys$search(...) ovmx_mmk_sys_search(__VA_ARGS__) +#define sys$filescan(...) ovmx_mmk_sys_filescan(__VA_ARGS__) +#define lib$getdvi(...) ovmx_mmk_lib_getdvi(__VA_ARGS__) +#define ots$cvt_tu_l(...) ovmx_mmk_ots_cvt_tu_l(__VA_ARGS__) +#define str$position(...) ovmx_mmk_str_position(__VA_ARGS__) +#define lib$get_symbol(...) ovmx_mmk_lib_get_symbol(__VA_ARGS__) +#define lib$set_logical(...) ovmx_mmk_lib_set_logical(__VA_ARGS__) +#define lib$create_vm_zone(...) ovmx_mmk_create_vm_zone(__VA_ARGS__) + +/* --- 4. va_count() replacement. DEC C's va_count(n) sets n to the number of + * arguments the current variadic function was called with, using the + * VAX/Alpha/I64 calling-standard argument-count register. The GCC/SysV + * x86-64 ABI has NO such register, so va_count cannot work at runtime. + * Instead we count the arguments at each CALL SITE with the standard + * preprocessor arg-counting trick (OVMX_NARG) and pass the count as a + * leading parameter. mmk.h's OVMX seam turns cat()/Define_Symbol() — + * the only two va_count users — into their ovmx_* counted forms; the + * function bodies (misc.c / symbols.c) read the passed count instead of + * calling va_count. (OVMX design choice, Rule 8.) ------------------- */ +#define OVMX_NARG(...) OVMX_NARG_(__VA_ARGS__, OVMX_RSEQ_N()) +#define OVMX_NARG_(...) OVMX_ARG_N(__VA_ARGS__) +#define OVMX_ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10,_11,_12,_13,_14,_15,_16, \ + _17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32, N,...) N +#define OVMX_RSEQ_N() \ + 32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17, \ + 16,15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#endif /* OVMX_MMK_COMPAT_H */ diff --git a/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_sp.c b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_sp.c new file mode 100644 index 000000000..d4c271365 --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/ovmx_mmk_sp.c @@ -0,0 +1,80 @@ +/* + * ovmx_mmk_sp.c - OVMX (vms-ec70) subprocess-manager companion for MMK. + * + * *** OVMX DESIGN CHOICE (clean-room, Rule 8) + DEFERRED-GAP FLAG *** + * + * MMK drives the compiler/linker by feeding resolved DCL command lines to a + * PERSISTENT DCL SUBPROCESS over a VMS MAILBOX, using a write-attention AST + + * $HIBER/$WAKE to know when each command finished and to capture $STATUS + * (build_target.c's send_cmd_and_wait / echo_ast protocol; the end-of-command + * markers MMK___WRITE "MMK____status=..."). Faithfully reproducing that needs + * OVMX executive support for lib$spawn-of-DCL + mailboxes + write-attention + * ASTs, which is NOT yet wired. Rather than FAKE command execution (which would + * be an INV-6 / Rule 9 facade — reporting success while nothing ran), this + * companion is HONEST about the boundary: + * + * - MMK's /NOACTION (dry-run) mode — which by design parses the description + * file, resolves all rules/symbols/dependencies, and PRINTS the exact + * commands it WOULD run without executing anything — is fully supported. + * The self-host spine's parse proof runs in this mode (see run_mmk_native.sh + * / test_mmk_parse_descrip). + * - A REAL build (executing commands) returns an honest failure status so MMK + * reports "cannot execute" and stops — it never pretends a build happened. + * + * Wiring the real DCL-subprocess drive (turning /NOACTION into a real build that + * invokes TCC.EXE / LINK.EXE / LIBRARIAN.EXE) is the remaining spine-#4 work, + * filed as a follow-up (see the vms-ec70 PR body). + */ +#include +#include +#include + +typedef void *SPHANDLE; + +/* MMK global: nonzero when running /NOACTION (dry run — nothing is executed). */ +extern int noaction; + +#ifndef SS$_UNSUPPORTED +#define SS$_UNSUPPORTED 0x00000924 /* even (failure) */ +#endif + +/* A single non-null sentinel handle: in dry-run there is no real subprocess, + * but MMK only checks the handle for non-zero (spctx == 0 => "not yet open"). */ +static int ovmx_sp_sentinel; + +unsigned int sp_open(SPHANDLE *ctxpp, void *inicmd, + unsigned int (*rcvast)(void *), void *rcvastprm) +{ + (void)inicmd; (void)rcvast; (void)rcvastprm; + if (!noaction) { + /* Honest: we cannot spawn a real DCL subprocess yet. */ + fprintf(stderr, + "%%MMK-E-NOSPAWN, OVMX build does not yet wire the DCL-subprocess " + "drive; re-run with /ACTION=NOACTION to see the build plan " + "(vms-ec70 deferred gap)\n"); + return SS$_UNSUPPORTED; + } + if (ctxpp) *ctxpp = &ovmx_sp_sentinel; + return SS$_NORMAL; +} + +/* In dry-run the only sends are the subprocess-setup commands (SET NOON, symbol + * definitions); accepting them has no observable effect because /NOACTION runs + * nothing. Never reached for a real build (sp_open already failed above). */ +unsigned int sp_send(SPHANDLE *ctxpp, void *cmdstr) +{ + (void)ctxpp; (void)cmdstr; + return noaction ? SS$_NORMAL : SS$_UNSUPPORTED; +} + +unsigned int sp_receive(SPHANDLE *ctxpp, void *rcvstr, int *rcvlen) +{ + (void)ctxpp; (void)rcvstr; + if (rcvlen) *rcvlen = 0; + return SS$_NORMAL; +} + +unsigned int sp_close(SPHANDLE *ctxpp) { (void)ctxpp; return SS$_NORMAL; } +unsigned int sp_show_subprocess(SPHANDLE ctx) { (void)ctx; return SS$_NORMAL; } + +/* NB: set_ctrlt_ast / clear_ctrlt_ast are provided by MMK's own misc.c. */ diff --git a/tests/corpus/tier3-mmk/ovmx/strdef.h b/tests/corpus/tier3-mmk/ovmx/strdef.h new file mode 100644 index 000000000..e8872fc1b --- /dev/null +++ b/tests/corpus/tier3-mmk/ovmx/strdef.h @@ -0,0 +1,4 @@ +/* OVMX (vms-ec70): empty shim for VMS — MMK includes it but uses nothing from it on the OVMX build. */ +#ifndef OVMX_SHIM_STRDEF_H +#define OVMX_SHIM_STRDEF_H +#endif diff --git a/tests/corpus/tier3-mmk/parse_descrip.c b/tests/corpus/tier3-mmk/parse_descrip.c index 3508b60d4..ba1126072 100644 --- a/tests/corpus/tier3-mmk/parse_descrip.c +++ b/tests/corpus/tier3-mmk/parse_descrip.c @@ -108,7 +108,9 @@ #include #pragma nostandard +#ifndef OVMX_MMK /* OVMX (vms-ec70): LIB$_SYNTAXERR is a macro in OVMX */ globalvalue unsigned int LIB$_SYNTAXERR; +#endif #pragma standard /* @@ -213,7 +215,18 @@ /* ** External references */ -#ifndef __VAX +#ifdef OVMX_MMK +/* OVMX (vms-ec70): drive the real lib$table_parse engine with the C port of + * PARSE_TABLES.MAR (bead vms-486, tests/libvms/mmk_parse_tables.c). The + * engine takes the grammar as its state_table and ignores key_table, so both + * &parse_state and &parse_key resolve to the grammar and no call site changes. */ +/* forward-declare only the grammar symbol (mmk_parse_tables.h also defines a + * PRS_K_ enum that would collide with MMK's own #defines below). */ +extern const TPA_GRAMMAR mmk_parse_descrip_grammar; +#define parse_state mmk_parse_descrip_grammar +#define parse_key mmk_parse_descrip_grammar +#define lib$tparse lib$table_parse +#elif !defined(__VAX) extern int parse_state, parse_key; unsigned int lib$table_parse(); #define lib$tparse lib$table_parse @@ -280,7 +293,11 @@ void parse_descrip (char *xline, int xlinelen, FILEHANDLE *newu, int *newmaxl, tpablk.tpa0.tpa$l_count = TPA_K_COUNT; tpablk.tpa0.tpa$l_options = TPA$M_BLANKS; tpablk.tpa0.tpa$l_stringcnt = linelen; +#ifdef OVMX_MMK /* OVMX (vms-ec70): 64-bit — do not truncate the pointer */ + tpablk.tpa0.tpa$l_stringptr = (char *)upline; +#else tpablk.tpa0.tpa$l_stringptr = (unsigned int)upline; +#endif tpablk.tpa_l_stringbase = line; tpablk.tpa_l_upbase = upline; tpablk.tpa_l_unit = newu; diff --git a/tests/corpus/tier3-mmk/parse_objects.c b/tests/corpus/tier3-mmk/parse_objects.c index e4c5fc7ba..4449b2ebf 100644 --- a/tests/corpus/tier3-mmk/parse_objects.c +++ b/tests/corpus/tier3-mmk/parse_objects.c @@ -86,7 +86,9 @@ #pragma module PARSE_OBJECTS "V1.8" #include "mmk.h" #pragma nostandard +#ifndef OVMX_MMK /* OVMX (vms-ec70): LIB$_SYNTAXERR is a macro in OVMX */ globalvalue int LIB$_SYNTAXERR; +#endif #pragma standard #include "globals.h" #include @@ -132,7 +134,13 @@ /* ** External references */ -#ifndef __VAX +#ifdef OVMX_MMK +/* OVMX (vms-ec70): drive lib$table_parse with the vms-486 object-list grammar. */ +extern const TPA_GRAMMAR mmk_parse_objects_grammar; +#define po_state mmk_parse_objects_grammar +#define po_key mmk_parse_objects_grammar +#define lib$tparse lib$table_parse +#elif !defined(__VAX) extern int po_state, po_key; unsigned int lib$table_parse(); #define lib$tparse lib$table_parse @@ -172,7 +180,11 @@ void Parse_Objects (char *line, int linelen, struct QUE *objque, int is_target) tpablk.tpa0.tpa$l_count = TPA_K_COUNT; tpablk.tpa0.tpa$l_options = TPA$M_BLANKS; tpablk.tpa0.tpa$l_stringcnt = linelen; +#ifdef OVMX_MMK /* OVMX (vms-ec70): 64-bit — do not truncate the pointer */ + tpablk.tpa0.tpa$l_stringptr = (char *)line; +#else tpablk.tpa0.tpa$l_stringptr = (unsigned int)line; +#endif tpablk.tpa_l_istarget = is_target; tpablk.tpa_l_objqptr = objque; diff --git a/tests/corpus/tier3-mmk/symbols.c b/tests/corpus/tier3-mmk/symbols.c index 1c0c5fea2..6c911f946 100644 --- a/tests/corpus/tier3-mmk/symbols.c +++ b/tests/corpus/tier3-mmk/symbols.c @@ -151,7 +151,9 @@ */ struct SYMBOL *Lookup_Symbol(char *); +#ifndef OVMX_MMK /* OVMX (vms-ec70): Define_Symbol is the counted form (mmk.h) */ void Define_Symbol(SYMTYPE, char *, char *, int, ...); +#endif int Resolve_Symbols(char *, int, char **, int *, int); void Clear_Local_Symbols(void); void Create_Local_Symbols(struct DEPEND *, struct OBJREF *, struct QUE *); @@ -372,7 +374,12 @@ struct SYMBOL *Lookup_Symbol (char *name) { ** **-- */ +#ifdef OVMX_MMK /* OVMX (vms-ec70): counted form; call-site macro (mmk.h) + * supplies ovmx_ac = total arg count in place of va_count. */ +void ovmx_Define_Symbol (int ovmx_ac, SYMTYPE symtype, char *name, char *val, int vallen, ...) { +#else void Define_Symbol (SYMTYPE symtype, char *name, char *val, int vallen, ...) { +#endif struct SYMBOL *sym; struct QUE *symq; @@ -382,7 +389,11 @@ void Define_Symbol (SYMTYPE symtype, char *name, char *val, int vallen, ...) { int actualcount, i; va_list ap; +#ifdef OVMX_MMK + actualcount = ovmx_ac; +#else va_count(actualcount); +#endif if (actualcount > 4) { va_start(ap, vallen); sep = va_arg(ap, char *); diff --git a/tests/libvms/mmk_parse_tables.c b/tests/libvms/mmk_parse_tables.c index cc3e637de..3d9c3f093 100644 --- a/tests/libvms/mmk_parse_tables.c +++ b/tests/libvms/mmk_parse_tables.c @@ -58,6 +58,7 @@ * what the ported state machine actually did — the state machine is real; only * the "store" side effect is a test probe. * -------------------------------------------------------------------------- */ +#ifndef OVMX_MMK_PRODUCTION static void mmk_record(mmk_parse_ctx *m, uint32_t code) { if (m->nevents >= MMK_MAX_EVENTS) return; @@ -70,7 +71,20 @@ static void mmk_record(mmk_parse_ctx *m, uint32_t code) { m->code[m->nevents] = code; m->nevents++; } +#endif /* !OVMX_MMK_PRODUCTION */ +#ifdef OVMX_MMK_PRODUCTION +/* OVMX (vms-ec70): PRODUCTION build — this grammar is compiled INTO MMK.EXE and + * its transitions must fire MMK's REAL store routines (parse_descrip.c's + * parse_store / parse_objects.c's parse_obj_store), which take the TPARSE block + * address and dispatch on tpa$l_param exactly like the .mar's PRS_STORE/PO_STORE. + * The test-probe bodies below (which record into a mmk_parse_ctx) are compiled + * out; the state machine itself is identical either way. */ +extern int parse_store(void *tpablk); +extern int parse_obj_store(void *tpablk); +static uint32_t act_prs(void *blk) { return (uint32_t)parse_store(blk); } +static uint32_t act_po (void *blk) { return (uint32_t)parse_obj_store(blk); } +#else static uint32_t act_prs(void *blk) { mmk_parse_ctx *m = (mmk_parse_ctx *)blk; uint32_t code = m->tpa.tpa$l_param; @@ -93,6 +107,7 @@ static uint32_t act_po(void *blk) { mmk_record(m, m->tpa.tpa$l_param); return SS$_NORMAL; } +#endif /* !OVMX_MMK_PRODUCTION */ /* -------------------------------------------------------------------------- * State indices (enum == file order, so TPA$K_NEXT_SEQ = index+1 is faithful). diff --git a/tests/toolchain/CMakeLists.txt b/tests/toolchain/CMakeLists.txt index 19556fcce..0facf4bc7 100644 --- a/tests/toolchain/CMakeLists.txt +++ b/tests/toolchain/CMakeLists.txt @@ -15,3 +15,76 @@ set_tests_properties(toolchain-olb-roundtrip PROPERTIES TIMEOUT 120 LABELS "toolchain;librarian;link" ) + +# ============================================================================= +# MMK.EXE — the vendored MadGoat MMK built as an OVMX image (self-host spine #4, +# bead vms-ec70). The 15 vendored make-engine TUs (unmodified except for tagged +# "OVMX (vms-ec70)" seams) + the OVMX companions + the vms-486 PARSE_TABLES C +# grammar, linked against the OVMX RTL. Proves MMK actually parses a descrip.mms +# through CLI$ compiled-CLD + real lib$table_parse + MMK's own parse_store, and +# emits the correct build plan. See tests/corpus/tier3-mmk/ovmx/ for the +# clean-room (Rule 8) build shim, and run_mmk_parse.sh for the end-to-end proof. +# +# NB: this host ctest links MMK against the OVMX RTL libraries directly; the +# VMS-native LINK.EXE/IMGACT packaging of the SAME sources is the mk_mmk.sh +# recipe (src/vmslink/mk_mmk.sh), exercised on the container native-link path. +# ============================================================================= +set(MMK_CORPUS ${CMAKE_SOURCE_DIR}/tests/corpus/tier3-mmk) +set(MMK_OVMX ${MMK_CORPUS}/ovmx) +set(MMK_COMPAT ${MMK_OVMX}/ovmx_mmk_compat.h) + +# Generate the embedded CLD source header from the vendored mmk_cld.cld. +set(MMK_CLD_SRC_H ${CMAKE_CURRENT_BINARY_DIR}/mmk_cld_src.h) +add_custom_command( + OUTPUT ${MMK_CLD_SRC_H} + COMMAND ${CMAKE_COMMAND} + -DIN=${MMK_CORPUS}/mmk_cld.cld -DOUT=${MMK_CLD_SRC_H} + -P ${CMAKE_CURRENT_SOURCE_DIR}/gen_mmk_cld_src.cmake + DEPENDS ${MMK_CORPUS}/mmk_cld.cld ${CMAKE_CURRENT_SOURCE_DIR}/gen_mmk_cld_src.cmake + COMMENT "Embedding mmk_cld.cld -> mmk_cld_src.h (vms-ec70)" +) + +add_executable(mmk_native + ${MMK_CORPUS}/mmk.c ${MMK_CORPUS}/parse_descrip.c + ${MMK_CORPUS}/parse_objects.c ${MMK_CORPUS}/build_target.c + ${MMK_CORPUS}/misc.c ${MMK_CORPUS}/mem.c + ${MMK_CORPUS}/fileio.c ${MMK_CORPUS}/symbols.c + ${MMK_CORPUS}/objects.c ${MMK_CORPUS}/default_rules.c + ${MMK_CORPUS}/get_rdt.c ${MMK_CORPUS}/readdesc.c + ${MMK_CORPUS}/str.c + ${CMAKE_SOURCE_DIR}/tests/libvms/mmk_parse_tables.c + ${MMK_OVMX}/ovmx_mmk_compat.c ${MMK_OVMX}/ovmx_mmk_cld.c + ${MMK_OVMX}/ovmx_mmk_sp.c ${MMK_OVMX}/ovmx_mmk_cms.c + ${MMK_OVMX}/ovmx_mmk_builtins.c + ${MMK_CLD_SRC_H} +) +target_include_directories(mmk_native PRIVATE + ${MMK_OVMX} ${MMK_CORPUS} ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/tests/libvms + ${CMAKE_SOURCE_DIR}/src/libvms/include + ${CMAKE_SOURCE_DIR}/src/vmsrms/include + ${CMAKE_SOURCE_DIR}/src/vmsfs/include + ${CMAKE_SOURCE_DIR}/src/vmslnm/include + ${CMAKE_SOURCE_DIR}/src/vmsprocess/include +) +target_compile_definitions(mmk_native PRIVATE OVMX_MMK __CRTL_VER=80400000 + _POSIX_C_SOURCE=200809L _DEFAULT_SOURCE) +target_compile_options(mmk_native PRIVATE + -fno-builtin -fno-stack-protector -Wno-unused -Wno-implicit-fallthrough + -include ${MMK_COMPAT}) +# The vms-486 grammar is compiled in PRODUCTION mode here: its transitions fire +# MMK's real parse_store / parse_obj_store (see mmk_parse_tables.c). +set_source_files_properties(${CMAKE_SOURCE_DIR}/tests/libvms/mmk_parse_tables.c + PROPERTIES COMPILE_DEFINITIONS OVMX_MMK_PRODUCTION) +target_link_libraries(mmk_native PRIVATE vms vmsrms vmsfs vmslnm vmsprocess vmssys pthread m) +set_target_properties(mmk_native PROPERTIES OUTPUT_NAME "MMK" SUFFIX ".EXE") + +add_test( + NAME toolchain-mmk-parse + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/run_mmk_parse.sh +) +set_tests_properties(toolchain-mmk-parse PROPERTIES + ENVIRONMENT "MMK_EXE=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/MMK.EXE" + TIMEOUT 120 + LABELS "toolchain;mmk;selfhost" +) diff --git a/tests/toolchain/run_mmk_parse.sh b/tests/toolchain/run_mmk_parse.sh new file mode 100755 index 000000000..22eee1d1a --- /dev/null +++ b/tests/toolchain/run_mmk_parse.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# run_mmk_parse.sh - MMK.EXE description-file PARSE + PLAN end-to-end proof +# (bead vms-ec70, self-host spine #4). +# +# Veracity (Q1/Q2): a REAL end-to-end through the actual files MMK reads. +# 1. A real descrip.mms is written to the OVMX system disk (/vms), naming a +# two-step build: target.EXE <- target.OBJ <- target.C with explicit +# TCC/LINK commands. +# 2. MMK.EXE is run as a foreign command ("/DESCRIPTION=... /NOACTION/LOG +# "). It reads the descrip.mms through OVMX RMS, parses it with the +# REAL lib$table_parse engine driven by the ported PARSE_TABLES.MAR grammar +# (bead vms-486) via MMK's own parse_store, and resolves the command line +# through the CLI$ compiled-CLD path (cli$compile_cld on mmk_cld.cld, bead +# vms-8c1). +# 3. In /NOACTION (dry-run) mode MMK emits the exact commands it WOULD run, in +# dependency order. The test asserts BOTH commands appear in the right +# order — proving MMK really built the target/dependency graph and resolved +# the inference chain, not that a script faked it. +# +# The independent oracle is the descrip.mms itself: the expected output is the +# TCC/LINK command bodies the file specifies, in the order the dependency graph +# dictates (source compiled before it is linked). +# +# ACTUAL command EXECUTION (turning the plan into a built image) rides MMK's +# DCL-subprocess/mailbox/AST drive, which is a separately-tracked deferred gap +# (see the vms-ec70 PR); this test proves the parse+plan half end-to-end. +# +# Inputs (env, set by CMake add_test): MMK_EXE. Exit 0 = success. +set -e + +: "${MMK_EXE:?need MMK_EXE (built MMK.EXE)}" +[ -x "$MMK_EXE" ] || { echo "FAIL: MMK_EXE not executable: $MMK_EXE"; exit 1; } + +# MMK opens the description file through OVMX RMS, which resolves a bare filespec +# against the process default directory (cwd). Work in an isolated temp dir and +# cd into it so the run is hermetic (no dependency on /vms contents). +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT +cd "$WORK" + +DESCRIP="MMK_SPINE_TEST.MMS" +SRC="MMKSPINE.C" +OBJ="MMKSPINE.OBJ" +EXE="MMKSPINE.EXE" + +# A real MMS description file: target <- object <- source, tab-indented command +# bodies (MMK requires the leading tab). +printf '%s : %s\n\tLINK %s\n\n%s : %s\n\tTCC %s\n' \ + "$EXE" "$OBJ" "$OBJ" "$OBJ" "$SRC" "$SRC" > "$DESCRIP" +printf 'int main(void){return 0;}\n' > "$SRC" +# /RULES defaults to MMS$RULES; provide an empty one so MMK does not warn about a +# missing default rules file (keeps the run's final status clean). +printf '! empty default rules (vms-ec70 parse test)\n' > "MMS\$RULES" + +echo "== descrip.mms ==" +sed 's/^/ /' "$DESCRIP" + +echo +echo "== MMK.EXE /DESCRIPTION=$DESCRIP /NOACTION/LOG $EXE ==" +OUT=$(mktemp) +set +e +VMS_FOREIGN_CMD="/DESCRIPTION=$DESCRIP /NOACTION/LOG $EXE" \ + "$MMK_EXE" < /dev/null > "$OUT" 2>/dev/null +RC=$? +set -e +echo "-- MMK exit=$RC (odd VMS status = success); stdout: --" +sed 's/^/ /' "$OUT" + +# VMS success status is ODD; exit(SS$_NORMAL=1) -> shell exit 1. +case "$RC" in + 1|0) : ;; # 1 == SS$_NORMAL low byte; 0 tolerated + *) if [ $((RC & 1)) -eq 0 ]; then echo "FAIL: MMK exited with an even (failure) status $RC"; rm -f "$OUT"; exit 1; fi ;; +esac + +# Assert the plan: TCC before LINK, both present with the right operands. +tcc_line=$(grep -n "^TCC $SRC\$" "$OUT" | head -1 | cut -d: -f1) +lnk_line=$(grep -n "^LINK $OBJ\$" "$OUT" | head -1 | cut -d: -f1) +rm -f "$OUT" + +[ -n "$tcc_line" ] || { echo "FAIL: expected 'TCC $SRC' in the plan (MMK did not resolve the .C->.OBJ step)"; exit 1; } +[ -n "$lnk_line" ] || { echo "FAIL: expected 'LINK $OBJ' in the plan (MMK did not resolve the .OBJ->.EXE step)"; exit 1; } +[ "$tcc_line" -lt "$lnk_line" ] || { echo "FAIL: plan order wrong — LINK must follow TCC (dependency order)"; exit 1; } + +echo +echo "================================================================================" +echo "MILESTONE (vms-ec70, self-host spine #4): MMK.EXE parsed a real descrip.mms via" +echo "the CLI\$ compiled-CLD + real lib\$table_parse + the vms-486 PARSE_TABLES grammar +" +echo "MMK's own parse_store engine, built the target/dependency graph, and emitted the" +echo "correct build plan in dependency order (TCC $SRC -> LINK $OBJ)." +echo "================================================================================" From 258e877286a7a3ec8fa9513dca7980e89538e6be Mon Sep 17 00:00:00 2001 From: alice Date: Thu, 13 Aug 2026 05:20:38 +0000 Subject: [PATCH 2/2] =?UTF-8?q?vms-ec70:=20fix=20CI=20=E2=80=94=20freeze?= =?UTF-8?q?=20lib$reset=5Fvm=5Fzone=20universal=20+=20ship=20the=20CLD-emb?= =?UTF-8?q?ed=20generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebased onto current origin/main (picks up the vms-bd1 frozen shareable-vector mechanism, which post-dated the original base). Two CI-red root causes fixed: 1. Native-link graph (LINK.EXE Graph / DCL native / LIBRARIAN / IMGACT / self- link, all RED): the new lib$reset_vm_zone was an UNFROZEN universal, so mk_libvms_shr.sh appended it and invoked `join` — absent from the alpine musl container (no coreutils) → Error 127, failing every job that rebuilds LIBVMS$SHR. Fix: append lib$reset_vm_zone=PROCEDURE to the END of libvms_shr.vec and libvms_shr.vec.frozen (append-only; existing indices/ GSMATCH unaffected). Verified in-container: "351 frozen + 0 appended", join skipped, link_native_graph produces 9 EM_X86_64 artifacts, zero DT_NEEDED. 2. Build & Test / Static Analysis / Conformance / Corpus (all RED building mmk_native): the CLD-embed generator was gen_mmk_cld_src.cmake, but .gitignore excludes *.cmake, so it never landed in a clean checkout ("No rule to make target gen_mmk_cld_src.cmake"). Fix: ship it as gen_mmk_cld_src.sh (not ignored) and invoke it from the custom command. Full ctest: 170/170 (was 167; main added 3). toolchain-mmk-parse + symvec-freeze green. No regressions from the rebase. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/vmslink/libvms_shr.vec | 1 + src/vmslink/libvms_shr.vec.frozen | 1 + tests/toolchain/CMakeLists.txt | 7 +++---- tests/toolchain/gen_mmk_cld_src.sh | 13 +++++++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100755 tests/toolchain/gen_mmk_cld_src.sh diff --git a/src/vmslink/libvms_shr.vec b/src/vmslink/libvms_shr.vec index 0f728e5a6..8a4b66bc3 100644 --- a/src/vmslink/libvms_shr.vec +++ b/src/vmslink/libvms_shr.vec @@ -386,3 +386,4 @@ vms_uring_init=PROCEDURE vms_uring_process_completions=PROCEDURE vms_uring_submit_rw=PROCEDURE vms_uring_wait_completion=PROCEDURE +lib$reset_vm_zone=PROCEDURE diff --git a/src/vmslink/libvms_shr.vec.frozen b/src/vmslink/libvms_shr.vec.frozen index 681d417a6..b0cfe58c4 100644 --- a/src/vmslink/libvms_shr.vec.frozen +++ b/src/vmslink/libvms_shr.vec.frozen @@ -348,3 +348,4 @@ vms_uring_init=PROCEDURE vms_uring_process_completions=PROCEDURE vms_uring_submit_rw=PROCEDURE vms_uring_wait_completion=PROCEDURE +lib$reset_vm_zone=PROCEDURE diff --git a/tests/toolchain/CMakeLists.txt b/tests/toolchain/CMakeLists.txt index 0facf4bc7..cb4c2cc52 100644 --- a/tests/toolchain/CMakeLists.txt +++ b/tests/toolchain/CMakeLists.txt @@ -37,10 +37,9 @@ set(MMK_COMPAT ${MMK_OVMX}/ovmx_mmk_compat.h) set(MMK_CLD_SRC_H ${CMAKE_CURRENT_BINARY_DIR}/mmk_cld_src.h) add_custom_command( OUTPUT ${MMK_CLD_SRC_H} - COMMAND ${CMAKE_COMMAND} - -DIN=${MMK_CORPUS}/mmk_cld.cld -DOUT=${MMK_CLD_SRC_H} - -P ${CMAKE_CURRENT_SOURCE_DIR}/gen_mmk_cld_src.cmake - DEPENDS ${MMK_CORPUS}/mmk_cld.cld ${CMAKE_CURRENT_SOURCE_DIR}/gen_mmk_cld_src.cmake + COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/gen_mmk_cld_src.sh + ${MMK_CORPUS}/mmk_cld.cld ${MMK_CLD_SRC_H} + DEPENDS ${MMK_CORPUS}/mmk_cld.cld ${CMAKE_CURRENT_SOURCE_DIR}/gen_mmk_cld_src.sh COMMENT "Embedding mmk_cld.cld -> mmk_cld_src.h (vms-ec70)" ) diff --git a/tests/toolchain/gen_mmk_cld_src.sh b/tests/toolchain/gen_mmk_cld_src.sh new file mode 100755 index 000000000..d06e2a3dd --- /dev/null +++ b/tests/toolchain/gen_mmk_cld_src.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# gen_mmk_cld_src.sh (vms-ec70) — embed a .cld file as a C string literal. +# Usage: gen_mmk_cld_src.sh +# Produces: static const char mmk_cld_source[] = "...."; +# (A shell script, not a *.cmake file, because .gitignore excludes *.cmake.) +set -e +IN=${1:?need input .cld}; OUT=${2:?need output .h} +{ + echo "/* generated from mmk_cld.cld by gen_mmk_cld_src.sh (vms-ec70) */" + echo "static const char mmk_cld_source[] =" + sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/ "/' -e 's/$/\\n"/' "$IN" + echo ";" +} > "$OUT"