Skip to content

Commit 7d636a4

Browse files
author
jan.nijtmans
committed
Merge 9.0
2 parents 4a148de + edc192b commit 7d636a4

File tree

12 files changed

+286
-131
lines changed

12 files changed

+286
-131
lines changed

.github/workflows/mac-build.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ permissions:
1111
contents: read
1212
jobs:
1313
xcode:
14-
runs-on: macos-14
14+
runs-on: macos-15
1515
defaults:
1616
run:
1717
shell: bash
@@ -36,7 +36,7 @@ jobs:
3636
MAC_CI: 1
3737
timeout-minutes: 15
3838
clang:
39-
runs-on: macos-14
39+
runs-on: macos-15
4040
strategy:
4141
matrix:
4242
config:

doc/timerate.n

+13-5
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ implementation.
7878
.
7979
The \fB\-overhead\fR parameter supplies an estimate (in microseconds, which may
8080
be a floating point number) of the
81-
measurement overhead of each iteration of the tested script. This quantity
82-
will be subtracted from the measured time prior to reporting results. This can
83-
be useful for removing the cost of interpreter state reset commands from the
84-
script being measured.
81+
measurement overhead of each iteration of the tested script. The passed value
82+
overrides, for the current invocation of \fBtimerate\fR, the overhead
83+
estimated by a previous calibration. Overrides may themselves be measured
84+
using \fBtimerate\fR as illustrated by a later example.
8585
.\" OPTION: -direct
8686
.TP
8787
\fB\-direct\fR
@@ -134,7 +134,7 @@ it to calculate:
134134

135135
\fI# estimate overhead\fR
136136
set tm 0
137-
set ovh [lindex [\fBtimerate\fR {
137+
set ovh [lindex [\fBtimerate -overhead 0\fR {
138138
incr tm [expr {24*60*60}]
139139
}] 0]
140140

@@ -145,6 +145,14 @@ set tm 0
145145
incr tm [expr {24*60*60}]; # overhead for this is ignored
146146
} 5000
147147
.CE
148+
.PP
149+
In this last example, note that the overhead itself is measured using
150+
\fBtimerate\fR invoked with \fB-overhead 0\fR. This is necessary
151+
because explicit overheads are assumed to be absolute values,
152+
and not an increment over the default calibrated overhead. It is
153+
therefore important that the calibrated overhead is excluded in the
154+
measurement of the overhead value itself. This is accomplished by
155+
passing \fB-overhead 0\fR when measuring the overhead.
148156
.SH "SEE ALSO"
149157
time(n)
150158
.SH KEYWORDS

generic/tclCmdMZ.c

+87-39
Original file line numberDiff line numberDiff line change
@@ -4134,18 +4134,30 @@ Tcl_TimeRateObjCmd(
41344134
int result, i;
41354135
Tcl_Obj *calibrate = NULL, *direct = NULL;
41364136
Tcl_WideUInt count = 0; /* Holds repetition count */
4137+
Tcl_WideUInt lastCount = 0; /* Repetition count since last calculation. */
41374138
Tcl_WideInt maxms = WIDE_MIN;
41384139
/* Maximal running time (in milliseconds) */
4139-
Tcl_WideUInt maxcnt = WIDE_MAX;
4140+
Tcl_WideUInt maxcnt = UWIDE_MAX;
41404141
/* Maximal count of iterations. */
41414142
Tcl_WideUInt threshold = 1; /* Current threshold for check time (faster
41424143
* repeat count without time check) */
4143-
Tcl_WideUInt maxIterTm = 1; /* Max time of some iteration as max
4144-
* threshold, additionally avoiding divide to
4145-
* zero (i.e., never < 1) */
4146-
unsigned short factor = 50; /* Factor (4..50) limiting threshold to avoid
4144+
Tcl_WideUInt avgIterTm = 1; /* Average time of all processed iterations. */
4145+
Tcl_WideUInt lastIterTm = 1;/* Average time of last block of iterations. */
4146+
double estIterTm = 1.0; /* Estimated time of next iteration,
4147+
* considering the growth of lastIterTm. */
4148+
#ifdef TCL_WIDE_CLICKS
4149+
# define TR_SCALE 10 /* Fraction is 10ns (from wide click 100ns). */
4150+
#else
4151+
# define TR_SCALE 100 /* Fraction is 10ns (from 1us = 1000ns). */
4152+
#endif
4153+
#define TR_MIN_FACTOR 2 /* Min allowed factor calculating threshold. */
4154+
#define TR_MAX_FACTOR 50 /* Max allowed factor calculating threshold. */
4155+
#define TR_FACT_SINGLE_ITER 25 /* This or larger factor value will force the
4156+
* threshold 1, to avoid drastic growth of
4157+
* execution time by quadratic O() complexity. */
4158+
unsigned short factor = 16; /* Factor (2..50) limiting threshold to avoid
41474159
* growth of execution time. */
4148-
Tcl_WideInt start, middle, stop;
4160+
Tcl_WideInt start, last, middle, stop;
41494161
#ifndef TCL_WIDE_CLICKS
41504162
Tcl_Time now;
41514163
#endif /* !TCL_WIDE_CLICKS */
@@ -4351,7 +4363,7 @@ Tcl_TimeRateObjCmd(
43514363
*/
43524364

43534365
#ifdef TCL_WIDE_CLICKS
4354-
start = middle = TclpGetWideClicks();
4366+
start = last = middle = TclpGetWideClicks();
43554367

43564368
/*
43574369
* Time to stop execution (in wide clicks).
@@ -4363,7 +4375,7 @@ Tcl_TimeRateObjCmd(
43634375
start = now.sec;
43644376
start *= 1000000;
43654377
start += now.usec;
4366-
middle = start;
4378+
last = middle = start;
43674379

43684380
/*
43694381
* Time to stop execution (in microsecs).
@@ -4444,61 +4456,93 @@ Tcl_TimeRateObjCmd(
44444456
}
44454457

44464458
/*
4447-
* Don't calculate threshold by few iterations, because sometimes
4448-
* first iteration(s) can be too fast or slow (cached, delayed
4449-
* clean up, etc).
4459+
* Average iteration time (scaled) in fractions of wide clicks
4460+
* or microseconds.
44504461
*/
44514462

4452-
if (count < 10) {
4453-
threshold = 1;
4454-
continue;
4455-
}
4456-
4457-
/*
4458-
* Average iteration time in microsecs.
4459-
*/
4460-
4461-
threshold = (middle - start) / count;
4462-
if (threshold > maxIterTm) {
4463-
maxIterTm = threshold;
4463+
threshold = (Tcl_WideUInt)(middle - start) * TR_SCALE / count;
4464+
if (threshold > avgIterTm) {
44644465

44654466
/*
44664467
* Iterations seem to be longer.
44674468
*/
44684469

4469-
if (threshold > maxIterTm * 2) {
4470+
if (threshold > avgIterTm * 2) {
44704471
factor *= 2;
4471-
if (factor > 50) {
4472-
factor = 50;
4473-
}
44744472
} else {
4475-
if (factor < 50) {
4476-
factor++;
4477-
}
4473+
factor++;
44784474
}
4479-
} else if (factor > 4) {
4475+
if (factor > TR_MAX_FACTOR) {
4476+
factor = TR_MAX_FACTOR;
4477+
}
4478+
} else if (factor > TR_MIN_FACTOR) {
44804479
/*
44814480
* Iterations seem to be shorter.
44824481
*/
44834482

4484-
if (threshold < (maxIterTm / 2)) {
4483+
if (threshold < (avgIterTm / 2)) {
44854484
factor /= 2;
4486-
if (factor < 4) {
4487-
factor = 4;
4485+
if (factor < TR_MIN_FACTOR) {
4486+
factor = TR_MIN_FACTOR;
44884487
}
44894488
} else {
44904489
factor--;
44914490
}
44924491
}
44934492

4493+
if (!threshold) {
4494+
/* too short and too few iterations */
4495+
threshold = 1;
4496+
continue;
4497+
}
4498+
avgIterTm = threshold;
4499+
44944500
/*
4495-
* As relation between remaining time and time since last check,
4496-
* maximal some % of time (by factor), so avoid growing of the
4497-
* execution time if iterations are not consistent, e.g. was
4498-
* continuously on time).
4501+
* Estimate last iteration time growth and time of next iteration.
44994502
*/
4503+
lastCount = count - lastCount;
4504+
if (last != start && lastCount) {
4505+
Tcl_WideUInt lastTm;
4506+
4507+
lastTm = (Tcl_WideUInt)(middle - last) * TR_SCALE / lastCount;
4508+
estIterTm = (double)lastTm / (lastIterTm ? lastIterTm : avgIterTm);
4509+
lastIterTm = lastTm > avgIterTm ? lastTm : avgIterTm;
4510+
} else {
4511+
lastIterTm = avgIterTm;
4512+
}
4513+
estIterTm *= lastIterTm;
4514+
last = middle; lastCount = count;
45004515

4501-
threshold = ((stop - middle) / maxIterTm) / factor + 1;
4516+
/*
4517+
* Calculate next threshold to check.
4518+
* Firstly check iteration time is not larger than remaining time,
4519+
* considering last known iteration growth factor.
4520+
*/
4521+
threshold = (Tcl_WideUInt)(stop - middle) * TR_SCALE;
4522+
/*
4523+
* Estimated count of iteration til the end of execution.
4524+
* Thereby 2.5% longer execution time would be OK.
4525+
*/
4526+
if (threshold / estIterTm < 0.975) {
4527+
/* estimated time for next iteration is too large */
4528+
break;
4529+
}
4530+
threshold /= estIterTm;
4531+
/*
4532+
* Don't use threshold by few iterations, because sometimes
4533+
* first iteration(s) can be too fast or slow (cached, delayed
4534+
* clean up, etc). Also avoid unexpected execution time growth,
4535+
* so if iterations continuously grow, stay by single iteration.
4536+
*/
4537+
if (count < 10 || factor >= TR_FACT_SINGLE_ITER) {
4538+
threshold = 1;
4539+
continue;
4540+
}
4541+
/*
4542+
* Reduce it by last known factor, to avoid unexpected execution
4543+
* time growth if iterations are not consistent (may be longer).
4544+
*/
4545+
threshold = threshold / factor + 1;
45024546
if (threshold > 100000) { /* fix for too large threshold */
45034547
threshold = 100000;
45044548
}
@@ -4648,6 +4692,10 @@ Tcl_TimeRateObjCmd(
46484692
TclReleaseByteCode(codePtr);
46494693
}
46504694
return result;
4695+
#undef TR_SCALE
4696+
#undef TR_MIN_FACTOR
4697+
#undef TR_MAX_FACTOR
4698+
#undef TR_FACT_SINGLE_ITER
46514699
}
46524700

46534701
/*

generic/tclIO.c

+15-4
Original file line numberDiff line numberDiff line change
@@ -6446,10 +6446,21 @@ ReadChars(
64466446
|| BytesLeft(bufPtr->nextPtr) == 0 || 0 ==
64476447
(statePtr->inputEncodingFlags & TCL_ENCODING_END));
64486448

6449-
Tcl_ExternalToUtf(NULL, encoding, src, srcLen,
6450-
(statePtr->inputEncodingFlags | TCL_ENCODING_NO_TERMINATE),
6451-
&statePtr->inputEncodingState, buffer, sizeof(buffer),
6452-
&read, &decoded, &count);
6449+
/*
6450+
* bug 73bb42fb: the result was not checked for an encoding error.
6451+
* So, add a check as above for testing.
6452+
* Leave eof check out, as typically only two characters are
6453+
* handled.
6454+
*/
6455+
6456+
code = Tcl_ExternalToUtf(NULL, encoding, src, srcLen,
6457+
(statePtr->inputEncodingFlags | TCL_ENCODING_NO_TERMINATE),
6458+
&statePtr->inputEncodingState, buffer, sizeof(buffer),
6459+
&read, &decoded, &count);
6460+
if (code == TCL_CONVERT_UNKNOWN || code == TCL_CONVERT_SYNTAX) {
6461+
SetFlag(statePtr, CHANNEL_ENCODING_ERROR);
6462+
code = TCL_OK;
6463+
}
64536464

64546465
if (count == 2) {
64556466
if (buffer[1] == '\n') {

generic/tclIcu.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,7 @@ TclIcuInit(
14341434
}
14351435
#endif // _WIN32
14361436

1437-
/* Symbol may have version (Windows, FreeBSD), or not (Linux) */
1437+
/* Symbol may have version (Linux), or not (Windows, FreeBSD) */
14381438

14391439
#define ICUUC_SYM(name) \
14401440
do { \

library/init.tcl

+4-2
Original file line numberDiff line numberDiff line change
@@ -590,8 +590,10 @@ proc auto_execok name {
590590
}
591591
set auto_execs($name) ""
592592

593-
set shellBuiltins [list assoc cls copy date del dir echo erase exit ftype \
594-
md mkdir mklink move rd ren rename rmdir start time type ver vol]
593+
set shellBuiltins [list assoc call cd cls color copy date del dir echo \
594+
erase exit ftype for if md mkdir mklink move path \
595+
pause prompt rd ren rename rmdir set start time \
596+
title type ver vol]
595597
if {[info exists env(PATHEXT)]} {
596598
# Add an initial ; to have the {} extension check first.
597599
set execExtensions [split ";$env(PATHEXT)" ";"]

tests/cmdMZ.test

+20
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,26 @@ test cmdMZ-6.12 {Tcl_TimeRateObjCmd: done optimization: nested call of self insi
485485
}
486486
list [lindex [timerate $m1 1000 5] 2] $x
487487
} {5 20}
488+
test cmdMZ-6.13 {Tcl_TimeRateObjCmd: stability by O(n**2), avoid long execution time on growing iteration time} {
489+
set result {}
490+
# test the function with quadratic complexity (iteration growth 2x, 10x, 100x):
491+
foreach e {2 10 100} {
492+
set x 1
493+
set m1 [timerate {
494+
apply {x {
495+
while {[incr x -1]} {}
496+
}} [set x [expr {$x*$e}]]
497+
} 50]
498+
lappend result "${e}x"
499+
# check it was too slow (it is OK to use factor 2 to prevent sporadic
500+
# errors on some slow systems or time issues, because if it is not fixed,
501+
# the execution time may grow hundreds and thousand times):
502+
if {[lindex $m1 6] > 50 * 2} {
503+
lappend result "unexpected long: $m1"
504+
}
505+
}
506+
set result
507+
} {2x 10x 100x}
488508

489509
test cmdMZ-try-1.0 {
490510

tests/exec.test

+24
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ source [file join [file dirname [info script]] tcltests.tcl]
2323
# Some skips when running in a macOS CI environment
2424
testConstraint noosxCI [expr {![info exists ::env(MAC_CI)]}]
2525

26+
# Need a App Exec Alias for testing exec of reparse points
27+
if {[info exists ::env(LOCALAPPDATA)] &&
28+
[file exists [file join $::env(LOCALAPPDATA) "Microsoft" "WindowsApps" "winget.exe"]]} {
29+
testConstraint haveWinget 1
30+
}
31+
2632
unset -nocomplain path
2733

2834
# Utilities that are like Bourne shell stalwarts, but cross-platform.
@@ -741,6 +747,24 @@ test exec-21.2 {exec encoding mismatch on stderr} -setup {
741747
list [catch {exec [info nameofexecutable] $path(script)} r] $r
742748
} -result [list 1 a\uFFFDb]
743749

750+
test exec-bug-4f0b5767ac {exec App Execution Alias} -constraints haveWinget -body {
751+
exec winget --info
752+
} -result "Windows Package Manager*" -match glob
753+
754+
foreach cmdBuiltin {
755+
assoc call cd cls color copy date del dir echo
756+
erase exit ftype for if md mkdir mklink move path
757+
pause prompt rd ren rename rmdir set start time
758+
title type ver vol
759+
} {
760+
test auto_execok-$cmdBuiltin-1.0 "auto_execok $cmdBuiltin" \
761+
-constraints win \
762+
-body {
763+
string equal [auto_execok $cmdBuiltin] \
764+
"[file normalize $::env(COMSPEC)] /c $cmdBuiltin"
765+
} -result 1
766+
}
767+
unset cmdBuiltin
744768

745769
# ----------------------------------------------------------------------
746770
# cleanup

tests/io.test

+26
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,32 @@ test io-13.12 {TranslateInputEOL: find EOF char in src} {
18551855
close $f
18561856
set x
18571857
} "\n\n\nab\n\nd"
1858+
test io-13.13 {Translation crlf: \r followed by encoding error before buffer boundary - TCL bug 73bb42fb}\
1859+
-setup {
1860+
set buffersize 8
1861+
writeFile $path(test1) binary\
1862+
[string repeat x [expr $buffersize-1]]\r\xe9XXXXXXXXXXXXXXXXXXXXXXXX
1863+
} -body {
1864+
set fd [open $path(test1)]
1865+
fconfigure $fd -encoding utf-8 -buffersize [expr {$buffersize+1}] -translation crlf
1866+
catch {read $fd $buffersize} e d
1867+
list [dict get $d -data] [dict get $d -code] [dict get $d -errorcode]
1868+
} -cleanup {
1869+
close $fd
1870+
} -result {xxxxxxx 1 {POSIX EILSEQ {invalid or incomplete multibyte or wide character}}}
1871+
test io-13.14 {Translation crlf: \r followed by encoding error after buffer boundary - TCL bug 73bb42fb (crash)}\
1872+
-setup {
1873+
set buffersize 8
1874+
writeFile $path(test1) binary\
1875+
[string repeat x [expr $buffersize-1]]\r\xe9XXXXXXXXXXXXXXXXXXXXXXXX
1876+
} -body {
1877+
set fd [open $path(test1)]
1878+
fconfigure $fd -encoding utf-8 -buffersize $buffersize -translation crlf
1879+
catch {read $fd $buffersize} e d
1880+
list [dict get $d -data] [dict get $d -code] [dict get $d -errorcode]
1881+
} -cleanup {
1882+
close $fd
1883+
} -result {xxxxxxx 1 {POSIX EILSEQ {invalid or incomplete multibyte or wide character}}}
18581884

18591885
# Test standard handle management. The functions tested are
18601886
# Tcl_SetStdChannel and Tcl_GetStdChannel. Incidentally we are

0 commit comments

Comments
 (0)