Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# kit (development version)
# kit 0.0.23 <small>(2026-11-02)</small>

### Bug Fixes

- Fix `nswitch()` with duplicate values and `nThread > 1`: the outer loop over value/output pairs is now serial and atomic-output pairs are parallelised over the elements of `x`, so the last matching pair wins deterministically. Also fix the list-input/logical-output path reading output element `i` instead of `j` (#63 follow-up).

- Fix `fduplicated()` on logical vectors longer than `INT_MAX`: the generic path used an `int` loop index and stored `i + 1` in an `int` table slot, so a multi-gigabyte logical vector could overflow and read out of bounds. It now raises the standard "Long vector not supported yet" error (#61 follow-up).

- Apply `xtfrm` to classed vectors before the `topn()` long-vector heap path, so custom classes follow `order()` semantics there too; classed long vectors whose `xtfrm` is not integer/double now error clearly instead of falling back to the integer-index `order()` path (#83 follow-up).

- Fix `nswitch()` segfaulting when called without value/output pairs (e.g. `nswitch(1:3)` or `nswitch(1:3, default = 0L)`): the argument count is now validated before any pair is read, and the call errors with a clear message (#87).

- Evaluate the internal `order()`/`sort()` calls in `topn`, `psort`, and `charToFact` in the base environment. A caller-defined `order`/`sort` could previously shadow them and make `topn` return out-of-bounds indices or `psort`/`charToFact` error or sort incorrectly (#74).
Expand All @@ -24,6 +30,8 @@

### Notes

- Mix hashed keys with a 32-bit avalanche before indexing the growable duplicate tables. The previous top-bits-only multiplicative hash mapped sequential integer IDs to only ~16% of the slots in a power-of-two table, so `funique()`/`fduplicated()`/`uniqLen()` fell back to long linear-probe scans on common ID-like data. The tables still start at 256 slots and grow on demand; adding `tools/benchmark-unique.R` keeps this case visible.

- Document `?kit` with a package page that explains OpenMP threads, platform support, encoding behavior, and the main functions. Add "OpenMP and threads" and "Platforms" sections to the README, and add `library(kit)` to the first README example (#90).

- Reduce peak memory in `funique`, `fduplicated`, `uniqLen`, `psort`, and `charToFact` by starting duplicate hash tables at 256 slots and growing them on demand (4x, capped at the previous full-size table) instead of allocating the full table up front. Rehashing re-reads the source values, so results are unchanged; inputs with few unique values use much less memory, while peak memory can be higher at growth boundaries for high-cardinality inputs (#74).
Expand Down
12 changes: 12 additions & 0 deletions inst/tinytest/test-long-vectors.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ expect_identical(funique(seq_len(10000L)), seq_len(10000L), info = "longv-0001.0
expect_identical(uniqLen(seq_len(10000L)), 10000L, info = "longv-0001.007")
expect_identical(topn(seq_len(10000L), 5L, decreasing = FALSE, hasna = FALSE), 1:5, info = "longv-0001.008")

# classed long vectors must not silently take the integer-index order() path:
# the xtfrm result is checked before selection and a non-numeric/short result
# errors. seq_len() is a compact ALTREP sequence, so this allocates nothing.
nlong <- 2^31 + 1000000
xl <- structure(seq_len(nlong), class = "kit_topn_long_bad")
registerS3method("xtfrm", "kit_topn_long_bad", function(x) 1L)
expect_error(topn(xl, 2L), pattern = "Classed long vectors are not supported by 'topn()'.",
fixed = TRUE, info = "longv-0001.126")
rm(xl, nlong)

# a data.frame with > INT_MAX cells is refused up front. All columns share one
# small logical vector, so building it is cheap and the error fires before
# dfToMatrix allocates the matrix.
Expand Down Expand Up @@ -93,6 +103,8 @@ invisible(gc())
lg <- rep(TRUE, n)
expect_identical(funique(lg), TRUE, info = "longv-0001.106")
expect_identical(uniqLen(lg), 1L, info = "longv-0001.107")
expect_error(fduplicated(lg), pattern = "Long vector not supported yet",
fixed = TRUE, info = "longv-0001.125")
rm(lg)
invisible(gc())

Expand Down
11 changes: 11 additions & 0 deletions inst/tinytest/test-nthread.R
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ expect_identical(
nswitch(xi, 1L, 10L, 2L, 20L, default = 0L, nThread = 2L),
info="nthread-nswitch-002"
)
# duplicate values: last matching pair wins; the pair loop is serial so the
# result cannot depend on thread scheduling (issue #63 follow-up)
expect_identical(
nswitch(rep(1L, n), 1L, 10L, 1L, 20L, default = 0L, nThread = 1L),
nswitch(rep(1L, n), 1L, 10L, 1L, 20L, default = 0L, nThread = 2L),
info="nthread-nswitch-dup-001"
)
expect_true(
all(nswitch(rep(1L, n), 1L, 10L, 1L, 20L, default = 0L, nThread = 2L) == 20L),
info="nthread-nswitch-dup-002"
)

# --- charToFact ---
expect_identical(
Expand Down
11 changes: 11 additions & 0 deletions inst/tinytest/test-vswitch-nswitch.R
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,17 @@ expect_identical(vswitch(c(1L, 2L, 3L), c(1L, 1L, 2L), c(10L, 20L, 30L)),
c(20L, 30L, NA_integer_), info="vswitch-0074.last-wins")
expect_identical(vswitch(c(1L, 2L, 3L), c(1L, 1L, 2L), list(10L, 20L, 30L)),
c(20L, 30L, NA_integer_), info="vswitch-0074.last-wins-list")
# issue #63 follow-up: duplicate nswitch values keep the last pair's result
expect_identical(nswitch(c(1L, 2L, 1L), 1L, 10L, 1L, 20L, default = 0L),
c(20L, 0L, 20L), info="nswitch-0074.last-wins-dup")
# list input + logical list output used the pair index instead of the element
# index when recycling vector outputs
expect_identical(
nswitch(list(1L, 1L, 2L),
list(1L), list(TRUE, FALSE, TRUE),
list(2L), list(FALSE, FALSE, TRUE),
default = list(FALSE)),
list(TRUE, FALSE, TRUE), info="nswitch-0074.list-output-index")
expect_identical(vswitch(c(1L, 2L, 3L, NA_integer_), c(1L, 2L), c(10L, 20L), 99L),
c(10L, 20L, 99L, 99L), info="vswitch-0074.default")
expect_identical(vswitch(c(1L, 2L, 3L, 4L), c(1L, 2L), c(10L, 20L), 91:94),
Expand Down
10 changes: 5 additions & 5 deletions man/topn.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ topn(vec, n=6L, decreasing=TRUE, hasna=TRUE, index=TRUE)

For inputs longer than \code{.Machine$integer.max}, \code{topn()} keeps a
heap of at most \code{n} entries instead, so memory stays proportional to
\code{n} (about 24 bytes per entry plus 8 bytes per returned index). Class
attributes are ignored and the stored values are compared. Unlike
\code{n} (about 24 bytes per entry plus 8 bytes per returned index). Unlike
\code{base::order()}, \code{decreasing = TRUE} works for these inputs. For
\code{n} above roughly three quarters of the input length this heap can use
more memory than a full-length \code{order()} index, and \code{order()} is
faster for \code{decreasing = FALSE}.

For classed numeric vectors shorter than \code{.Machine$integer.max},
\code{xtfrm()} is applied once before selection, so their order matches
\code{order()}/\code{xtfrm()}.
For classed numeric vectors, \code{xtfrm()} is applied once before
selection, so their order matches \code{order()}/\code{xtfrm()}. This also
applies to long inputs; a classed long vector whose \code{xtfrm()} result is
not integer or double errors.
}
\value{
If \code{index = TRUE}, an integer vector of the indices of the \code{n}
Expand Down
6 changes: 6 additions & 0 deletions src/dup.c
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,9 @@ SEXP dupVecR(SEXP x, SEXP uniq, SEXP fromLast) {
Kbase++;
}
} else if (tx == LGLSXP) {
if (n > INT_MAX) {
error("Length of 'x' is too large. (Long vector not supported yet)"); // # nocov
}
M = 4;
K = 2;
Mbase = 4;
Expand Down Expand Up @@ -1429,6 +1432,9 @@ SEXP dupVecIndexOnlyR(SEXP x) {
Kbase++;
}
} else if (tx == LGLSXP) {
if (n > INT_MAX) {
error("Length of 'x' is too large. (Long vector not supported yet)"); // # nocov
}
M = 4;
K = 2;
Mbase = 4;
Expand Down
8 changes: 7 additions & 1 deletion src/kit.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@
#define RCHAR(x, y) CHAR(STRING_ELT(x, y))
#define SEXP_F ScalarLogical(FALSE)
#define SEXP_T ScalarLogical(TRUE)
#define HASH(key, K) (3141592653U * (unsigned int)(key) >> (32 - (K)))
static inline size_t kit_hash_key(uint32_t key, int K) {
// avalanche so sequential integer keys do not collide in power-of-two tables
key ^= key >> 16;
key *= 0x9E3779B1U;
return (size_t)(key >> (32 - K));
}
#define HASH(key, K) kit_hash_key((uint32_t)(key), (K))
#define N_ISNAN(x, y) (!ISNAN(x) && !ISNAN(y))
#define B_IsNA(x, y) (R_IsNA(x) && R_IsNA(y))
#define B_IsNaN(x, y) (R_IsNaN(x) && R_IsNaN(y))
Expand Down
42 changes: 21 additions & 21 deletions src/nswitch.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
switch(type_x) {
case LGLSXP: {
const int *restrict px = LOGICAL(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = LOGICAL(PTR_ETL(args, 2*i+1));
const int *restrict pvalues = LOGICAL(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -161,10 +161,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case INTSXP: {
const int *restrict px = INTEGER(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = LOGICAL(PTR_ETL(args, 2*i+1));
const int *restrict pvalues = INTEGER(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -174,10 +174,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case REALSXP: {
const double *restrict px = REAL(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = LOGICAL(PTR_ETL(args, 2*i+1));
const double *restrict pvalues = REAL(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -187,10 +187,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case CPLXSXP: {
const Rcomplex *restrict px = COMPLEX(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = LOGICAL(PTR_ETL(args, 2*i+1));
const Rcomplex *restrict pvalues = COMPLEX(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (EQUAL_CPLX(px[j], pvalues[0])) {
pans[j] = pto[j & amask[i]];
Expand All @@ -200,10 +200,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case STRSXP: {
const SEXP *restrict px = STRING_PTR_RO(utfcon ? xans : x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = LOGICAL(PTR_ETL(args, 2*i+1));
const SEXP *restrict pvalues = STRING_PTR_RO(utfcon ? vans : PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -218,7 +218,7 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
const SEXP *restrict pvalues = SEXPPTR_RO(PTR_ETL(args, 2*i));
for (ssize_t j = 0; j < len_x; ++j) {
if (R_compute_identical(px[j],pvalues[0],0)) {
pans[j] = pto[i & amask[i]];
pans[j] = pto[j & amask[i]];
}
}
}
Expand All @@ -241,10 +241,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
switch(type_x) {
case LGLSXP: {
const int *restrict px = LOGICAL(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = INTEGER(PTR_ETL(args, 2*i+1));
const int *restrict pvalues = LOGICAL(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -254,10 +254,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case INTSXP: {
const int *restrict px = INTEGER(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = INTEGER(PTR_ETL(args, 2*i+1));
const int *restrict pvalues = INTEGER(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -267,10 +267,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case REALSXP: {
const double *restrict px = REAL(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = INTEGER(PTR_ETL(args, 2*i+1));
const double *restrict pvalues = REAL(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -280,10 +280,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case CPLXSXP: {
const Rcomplex *restrict px = COMPLEX(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = INTEGER(PTR_ETL(args, 2*i+1));
const Rcomplex *restrict pvalues = COMPLEX(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (EQUAL_CPLX(px[j], pvalues[0])) {
pans[j] = pto[j & amask[i]];
Expand All @@ -293,10 +293,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case STRSXP: {
const SEXP *restrict px = STRING_PTR_RO(utfcon ? xans : x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const int *restrict pto = INTEGER(PTR_ETL(args, 2*i+1));
const SEXP *restrict pvalues = STRING_PTR_RO(utfcon ? vans : PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand Down Expand Up @@ -334,10 +334,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
switch(type_x) {
case LGLSXP: {
const int *restrict px = LOGICAL(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const double *restrict pto = REAL(PTR_ETL(args, 2*i+1));
const int *restrict pvalues = LOGICAL(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -347,10 +347,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case INTSXP: {
const int *restrict px = INTEGER(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const double *restrict pto = REAL(PTR_ETL(args, 2*i+1));
const int *restrict pvalues = INTEGER(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -360,10 +360,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case REALSXP: {
const double *restrict px = REAL(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const double *restrict pto = REAL(PTR_ETL(args, 2*i+1));
const double *restrict pvalues = REAL(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -373,10 +373,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case CPLXSXP: {
const Rcomplex *restrict px = COMPLEX(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const double *restrict pto = REAL(PTR_ETL(args, 2*i+1));
const Rcomplex *restrict pvalues = COMPLEX(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (EQUAL_CPLX(px[j], pvalues[0])) {
pans[j] = pto[j & amask[i]];
Expand All @@ -386,10 +386,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case STRSXP: {
const SEXP *restrict px = STRING_PTR_RO(utfcon ? xans : x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const double *restrict pto = REAL(PTR_ETL(args, 2*i+1));
const SEXP *restrict pvalues = STRING_PTR_RO(utfcon ? vans : PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand Down Expand Up @@ -428,10 +428,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
switch(type_x) {
case LGLSXP: {
const int *restrict px = LOGICAL(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const Rcomplex *restrict pto = COMPLEX(PTR_ETL(args, 2*i+1));
const int *restrict pvalues = LOGICAL(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -441,10 +441,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case INTSXP: {
const int *restrict px = INTEGER(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const Rcomplex *restrict pto = COMPLEX(PTR_ETL(args, 2*i+1));
const int *restrict pvalues = INTEGER(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -454,10 +454,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case REALSXP: {
const double *restrict px = REAL(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const Rcomplex *restrict pto = COMPLEX(PTR_ETL(args, 2*i+1));
const double *restrict pvalues = REAL(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand All @@ -467,10 +467,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case CPLXSXP: {
const Rcomplex *restrict px = COMPLEX(x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const Rcomplex *restrict pto = COMPLEX(PTR_ETL(args, 2*i+1));
const Rcomplex *restrict pvalues = COMPLEX(PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (EQUAL_CPLX(px[j],pvalues[0])) {
pans[j] = pto[j & amask[i]];
Expand All @@ -480,10 +480,10 @@ SEXP nswitchR(SEXP x, SEXP na, SEXP nthreads, SEXP chkenc, SEXP args) {
} break;
case STRSXP: {
const SEXP *restrict px = STRING_PTR_RO(utfcon ? xans : x);
OMP_PARALLEL_FOR(nth)
for (ssize_t i = 0; i < n; ++i) {
const Rcomplex *restrict pto = COMPLEX(PTR_ETL(args, 2*i+1));
const SEXP *restrict pvalues = STRING_PTR_RO(utfcon ? vans : PTR_ETL(args, 2*i));
OMP_PARALLEL_FOR(nth)
for (ssize_t j = 0; j < len_x; ++j) {
if (px[j] == pvalues[0]) {
pans[j] = pto[j & amask[i]];
Expand Down
Loading
Loading