fix(compile): make the fs facade emit IL-verifiable methods (#1246)#1248
Conversation
Compiling any program that imports `fs` produced 68 IL verification errors in the emitted facade module. The program ran (the JIT tolerates the mismatches), but `--compile … --verify` could not gate any fs-using program. Two shapes, both a value stored into a narrower slot without the bridge the verifier requires: 1. `object` → `string`: a $M_fs helper stages an `any`-typed argument (e.g. `__joinPath(src, names[i])` in cpSync) into a `string` parameter temp, but `EmitConversionForParameter` handled only value-type targets (unbox) and fell through for reference targets, leaving `object` where `string` was expected. Fix: emit `castclass` on string parameter coercion. Restricted to `string` — the one non-object reference slot whose runtime value is a genuine CLR instance; delegates ($TSFunction), collections ($Array), and Task ($Promise) keep their prior object store. 2. `$Promise` → `Task<object>`: the promise primitives return a wrapped `$Promise`, which the callback helpers (`__cbData`/`__cbVoid`) took in a `Promise<any>` → `Task<object>` parameter slot. `ParameterTypeResolver` widens other unsound slots to object but never the non-async `Promise<T>` parameter case — the symmetric gap to the return-slot widening #393 already applies. Fix: widen `Promise<T>` (→ Task/Task<object>) parameter slots to object in `CoerceParamSlotType`. Adds regression tests: the whole-fs-facade verify (the issue repro) plus a minimal plain-TS repro of each class. xUnit 15415/0, Test262 22/0/4skip, TSConf 31/0, --verify clean.
…y CI) Process_Uptime_IncreasesOverTime(mode: Interpreted) intermittently failed on CI with `up2 >= up1` == false — uptime() ran backwards. Root cause is independent of this branch's IL-compiler changes (it's the interpreter path): `uptime()` returns `now - (_scriptStartTimestamp ?? _startTimestamp)`, and `_scriptStartTimestamp` was a *shared* mutable static that the in-process test runner toggles per run (SetScriptArguments sets a recent timestamp, ClearScriptArguments nulls it). Because xUnit parallelizes collections across one CLR process, a concurrent script's SetScriptArguments could land between another script's two uptime() reads: the first read used the null → process-start baseline (large elapsed), the second the just-set recent baseline (~0 elapsed), so uptime appeared to decrease. The earlier Stopwatch switch fixed clock monotonicity but not this baseline race. Fix: make `_scriptStartTimestamp` [ThreadStatic] so each script gets a stable per-thread baseline — same precedent as the ThreadArgv/ThreadEnv fields directly above it. Not a production bug: a real single-script process sets the baseline once on its own thread and has no concurrent script to clear it. Full suite green under the CI filter (Category!=LiveNetwork&Category!=LoadSensitive): 15408/0.
CI investigation: Ubuntu failure was a pre-existing flaky test (now fixed)The first CI run failed only on ubuntu-latest (windows-latest passed), on a single test: This is unrelated to the fix in this PR (which is IL-compiler-only): the failing test exercises the interpreter path, in Root cause
Fix (commit
|
Closes #1246.
Problem
Compiling any program that imports
fsproduced 68 IL verification errors in the emitted facade module. The program ran fine (the JIT tolerates the mismatches), but--compile … --verifycouldn't be a clean gate on any fs-using program.Both shapes are the same underlying defect — a value stored into a narrower slot without the bridge the verifier requires — surfacing in two forms.
Root causes & fixes
1.
object→string(sync helpers + display-classInvokes).A
$M_fshelper stages anany-typed argument (e.g.__joinPath(src, names[i])incpSync, wherenames[i]is anobject) into astringparameter temp.EmitConversionForParameterhandled only value-type targets (unbox) and fell through for reference targets, leavingobjecton the stack wherestringwas expected — nocastclass. Fixed by emittingcastclasson string parameter coercion (in both theILEmitteroverride and the shared base). Restricted tostring: it's the one non-object reference slot whose runtime value is a genuine CLR instance of the slot. Delegates ($TSFunction), dynamic collections ($Array/$Map/$Set), and Task ($Promise) carry a boxed-differently runtime value, so acastclasswould throw — they keep their prior (JIT-tolerated) object store.2.
$Promise→Task<object>(async callback/promise helpers).The promise primitives return a wrapped
$Promise, which the callback helpers (__cbData/__cbVoid) took in aPromise<any>→Task<object>parameter slot.ParameterTypeResolver.CoerceParamSlotTypealready widens other unsound parameter slots toobject(Date/RegExp/array/Map/Set, unions admittingundefined, symbols) — andResolveReturnTypewidens non-asyncPromise<T>returns to object (#393) — but the symmetric widening forPromise<T>parameters was never added. Fixed by wideningPromise<T>(→Task/Task<object>) parameter slots toobject, mirroring #393. APromise<T>param always receives the runtime$Promiseobject regardless of whether the enclosing function is async (only the function's return builds a real Task), so the widening is unconditional.With the parameter slots honest (
object), no runtime bridging is needed and the$Promisestores cleanly.Testing
ILVerificationTests:FsFacadeImport_PassesILVerification(the issue repro, whole facade), plus a minimal plain-TS repro of each class (ObjectArgumentToStringParameter_*,PromiseValueToPromiseParameter_*). Each was confirmed to fail pre-fix and pass post-fix.--verifyand--standalone --verifyclean on the repro; interp/compiled output identical across a broad fs exercise (sync + callback +fs/promises).No behavior change for correctly-typed programs — the fix only makes the already-working IL verifiable. Found incidentally while running
--verifyduring #1242; not a regression from it.