1.0.8 - Three ways into an evaluation, and the mask now covers all of them
Pre-releaseThere are three ways to start an evaluation. The 1.0.7 release put the floating
point exception mask on two of them. This one covers the third, which happens to
be the one the accelerator documentation recommends.
Compiled code ran without the mask
CompileScript returns an object you evaluate directly, and docs/USAGE.md
points at it for evaluating from several threads: it holds no shared state, so
each thread can have its own. That path never installed the mask. Division by
zero raised EZeroDivide from a worker thread instead of answering infinity -
the same defect 1.0.7 fixed, on the entry point most likely to be used from a
thread.
tests/JitContractTest.dpr now drives all three entry points through the same
formula and requires the same answer.
A nested parser ignored its own ExceptionMask
A parser called from inside another parser's evaluation used to inherit whatever
mask the caller had installed. The test for "is this the outermost evaluation"
asked whether any frame existed in the thread, and a foreign frame counts, so the
inner parser concluded it was nested and installed nothing at all. Its
ExceptionMask property could say something different and quietly mean nothing.
The test now compares the mask that is installed against the one this parser
wants. tests/FpuMaskTest.dpr covers it in 17 checks.
ExceptionMask is public
The property is documented as yours to narrow, and was declared protected,
which put it out of reach of the code that was supposed to narrow it. Reaching it
took a descendant class for no reason other than the visibility.
A nested loop guard switched off the outer cancellation
ArmLoopGuard with a budget of its own but no flag of its own wrote nil over
the cancellation flag that was already there. An owner asking the work to stop
went unheard for the whole of the inner run. An inner run may replace the budget,
which is a measure of this run; it may not replace the cancellation, which is
about all of the work. tests/LoopScopeTest.dpr, 19 checks.
Name lookup asked the memory manager twice
Looking a name up in the parser tables converted the string to lower case once
for the hash and once for the comparison - same string, same result, two trips to
the allocator. Deriv went from fourteen allocations per call to eleven, and
from 4.05 to 3.40 microseconds.
Worth knowing where that leaves things. On four threads the allocator is the
limit: an allocate-and-free pair takes 4.69 times as long as on one thread, while
moving the same bytes takes 1.91 and comparing the same strings 1.02. Fewer
allocations is the only lever that moves, and there are still eleven.
Compatibility
Delphi 13, Free Pascal 3.2.2 and 3.3.1. Nothing in the public interface changed
except ExceptionMask becoming visible.