winget againThe official Windows route is
winget install Git.Git OCaml.opam then
opam init. winget install blocks indefinitely with no output under automation — the same
failure as the Go track, for the same reason: it wants a session it can
raise an elevation prompt from.
Worth recording that winget search works fine and fast:
$ winget search --id OCaml.opam
Name Id Version Source
opam OCaml.opam 2.5.2 winget
So winget is not broken here; install specifically is unusable from this context. That is a
more precise statement than the Go notes made, and it corrects them.
The way around it was not a workaround at all: MSYS2 already has OCaml, in the same UCRT64 repo the C track's compiler comes from.
pacman -S --needed mingw-w64-ucrt-x86_64-ocaml # 28 MB down, 174 MB installed
pacman -S --needed mingw-w64-ucrt-x86_64-flexdll # needed to link, see wall 3
No elevation, two minutes, OCaml 5.4.1. What it does not give you is opam, and therefore any library at all. Fine for a stdlib-only program; a dead end the moment the track needs one.
Error: Unbound module StdlibFirst compile, on a two-line hello world:
File "command line", line 1:
Error: Unbound module Stdlib
The compiler cannot find its own standard library. ocamlopt -config says why:
standard_library_default: /ucrt64/lib/ocaml
standard_library: /ucrt64/lib/ocaml
os_type: Win32
/ucrt64/lib/ocaml is an MSYS path. It resolves inside an MSYS shell and nowhere else —
not from PowerShell, not from subprocess.run, not from git-bash calling the binary
directly. The override is OCAMLLIB:
export OCAMLLIB='C:\msys64\ucrt64\lib\ocaml'
This is the same bug, in the same MSYS2 installation, as the COBOL lab's COB_CONFIG_DIR
trap — a native Windows binary with a compiled-in Unix-style path. Second occurrence, so it
is a pattern rather than an incident: any compiler installed from MSYS2 that shells out to
its own data directory will need its path variable overridden with a real Windows path.
Worth checking first, not fifth, next time a UCRT64 toolchain fails to find itself.
With OCAMLLIB set, compilation succeeded and linking did not:
'C:\msys64\ucrt64\bin\flexlink.exe' was blocked by your organization's Device Guard policy.
File "caml_startup", line 1:
Error: Error during linking (exit code 4551)
flexlink is the linker wrapper ocamlopt shells out to for native Windows executables.
ocamlc.exe and ocamlc.byte.exe are blocked by the same policy; ocamlopt.exe and
ocamlrun.exe are not. The allowlist is not obviously principled from the outside, and it is
not mine to change.
This is a machine policy, not a puzzle. No attempt was made to get around it. What the track does instead is take the route the policy already permits:
ocamlrun ocamlc.byte -o build/logagg.byte logagg.ml # compile to bytecode
ocamlrun build/logagg.byte access.log # run it
ocamlc.byte is itself an OCaml bytecode image, so it is driven by ocamlrun rather than
executed. ocamlrun is a permitted interpreter reading a data file — structurally the same
as java -cp build LogAgg in the Java track, python logagg.py in the Python one, and
cobcrun in the COBOL lab, which made the same accommodation for the same policy.
What it costs: bytecode is roughly an order of magnitude slower than native for compute-bound
work. Irrelevant for a 200-line fixture. It is the reason --shard N is still outstanding —
benchmarking a parallel implementation on an interpreter would measure the interpreter.
ocamlopt ──► .cmx ──► .o ──► flexlink ╳ Device Guard (native, blocked)
ocamlc ╳ Device Guard (blocked directly)
ocamlrun ocamlc.byte ──► .byte ──► ocamlrun ✓ (what this track does)
Not a type error. A paren:
| _ -> assert false
(* ...comment... *));
*)); is *) then one ). Two were open — the (if … else …) and the
| None -> ( — so the file ran off the end looking for the second, and the error surfaced
17 lines later:
File "logagg.ml", line 197, characters 6-8:
197 | in
^^
Error: Syntax error: ) expected
File "logagg.ml", line 177, characters 12-13:
This ( might be unmatched
The second half of that message — pointing at the probable opening paren — is better than
most compilers manage, and it was still 17 lines from the real fix. Rewriting the block with
begin … end instead of parentheses fixed it and is more readable besides. OCaml's begin
and end are exactly ( and ); using them for multi-line blocks is the convention for
this reason.
Everything after that compiled clean under -w +a-4-9-40-41-42-44-45-70 and matched the
golden file on the first run. The whole-file experience: one syntax error, zero type errors,
zero logic errors. That is the inference doing its job — the places a type error would have
appeared are the places the other tracks put runtime bugs.
Same experiment as the Go track's, and the result differs in an instructive way.
OCaml's Hashtbl can randomise its seed — Hashtbl.create ~random:true per table, or R in
OCAMLRUNPARAM for every table in the process. Unlike Go, randomisation is off by
default, so the Go-style "fails immediately on your laptop" property is opt-in here.
for i in 1 2 3; do OCAMLRUNPARAM=R ocamlrun build/logagg.byte access.log > r$i.out; done
All three byte-identical to bench/expected/logagg.txt. SPEC 3's mandatory tie-break is what
makes that true; without it the bucket order would leak into the report.
So: Go found the justification for that clause by breaking without it, and OCaml confirms it by not breaking with randomisation forced on. Two tracks, two directions, same conclusion — which is worth more than either on its own.
A verify.py run immediately after mv-ing the track directory died with:
OSError: [WinError 4551] An Application Control policy has blocked this file
Re-running it, unchanged, gave 5 passed, 0 failed, 0 skipped. The most likely explanation
is a race between a freshly written build artifact and the Application Control scan of it —
verify.py rebuilds every track on every run, so there is always a new unsigned binary a few
milliseconds old. Not reproduced since. Logged rather than explained, because an intermittent
policy block on a machine that also blocks flexlink is worth being able to look up later.
--shard N (SPEC 6) with Domain.spawn and a merge fold. Needs native compilation
to benchmark meaningfully, so it is blocked behind the Device Guard question.Base, Eio, or ocamlformat needs the real toolchain.logagg.mli and see what it costs to make stats abstract. Q4 claims
signatures are the strongest access control available; the claim is untested in this
track.-warn-error +8 in the build, so a non-exhaustive match fails rather than warns. Q8
recommends it and the build script does not yet do it.