Skip to content

Commit dcf4952

Browse files
BAder82tpascoec
authored andcommitted
fix: close issue #249 — Emscripten OOM in CKKS unit tests
PR #1179 added ClearBootstrapPrecom and ClearSchemeSwitchPrecom helpers but did not wire them into ReleaseAllContexts. This change completes that work, then re-enables the EVAL_FAST_ROTATION and CONTEXT_WITH_SERTYPE tests that PR #985 had to guard under !defined(__EMSCRIPTEN__) for the original std::bad_alloc failure. Library changes (no hot-path touched): - ReleaseAllContexts now walks every live context and calls ClearAllCKKSCaches before clearing the static maps. - Adds malloc_zone_pressure_relief(Apple) and _heapmin(MSVC) to AllocTrim. - Add ClearAllCKKSCaches convenience on CryptoContextImpl. - ClearBootstrapPrecom and ClearSchemeSwitchPrecom bodies made noexcept-safe so the walk does not throw on contexts that have not Enable(FHE)'d or Enable(SCHEMESWITCH)'d. Test changes: - Mid-test ClearAllCKKSCaches between JSON and BINARY rounds in UnitTestCKKSrnsSerialize. - UTCKKSCacheClear extends its WITH_TCM skip to __EMSCRIPTEN__ (the heap-probe helper has no emmalloc backend). - Remove the !defined(__EMSCRIPTEN__) guards around the affected rows. CI: - Add .github/workflows/emscripten.yml: builds with emsdk 3.1.59 and runs pke_tests/core_tests under node in two slices. Validated on macOS + WASM: - Native: pke_tests 1892/1892 pass, core_tests 158/158 pass. - WASM: CKKS slice 939/941 pass (2 heap-probe tests skipped), BFV+BGV+binfhe slice 324/324 pass, peak 1.6 GB. Closes #249.
1 parent dd8d374 commit dcf4952

8 files changed

Lines changed: 109 additions & 17 deletions

File tree

.github/workflows/emscripten.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Emscripten
2+
3+
# Builds with emcc and runs the WASM unit tests under node. Guards against
4+
# re-introducing the cache-leak class that issue #249 tracked.
5+
6+
on:
7+
pull_request:
8+
branches: [dev]
9+
push:
10+
branches: [dev, main]
11+
workflow_dispatch:
12+
13+
jobs:
14+
build-and-test:
15+
runs-on: ubuntu-22.04
16+
timeout-minutes: 60
17+
steps:
18+
- uses: actions/checkout@v5
19+
- uses: actions/setup-node@v5
20+
with: { node-version: '20' }
21+
22+
- name: Cache emsdk
23+
id: cache-emsdk
24+
uses: actions/cache@v4
25+
with:
26+
path: ~/emsdk
27+
key: emsdk-3.1.59
28+
29+
- name: Install emsdk
30+
if: steps.cache-emsdk.outputs.cache-hit != 'true'
31+
run: |
32+
git clone --depth 1 --branch 3.1.59 https://github.com/emscripten-core/emsdk.git ~/emsdk
33+
~/emsdk/emsdk install 3.1.59
34+
~/emsdk/emsdk activate 3.1.59
35+
36+
- name: Configure
37+
run: |
38+
source ~/emsdk/emsdk_env.sh
39+
emcmake cmake -S . -B build \
40+
-DCMAKE_BUILD_TYPE=Release -DBUILD_UNITTESTS=ON \
41+
-DBUILD_BENCHMARKS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_EXTRAS=OFF \
42+
-DBUILD_STATIC=ON -DBUILD_SHARED=OFF -DWITH_OPENMP=OFF -DNATIVE_SIZE=64 \
43+
-DCMAKE_EXE_LINKER_FLAGS="-s INITIAL_MEMORY=536870912 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=4294967296 -s WASM=1 -s NODERAWFS=1 -s STACK_SIZE=5MB -s ASSERTIONS=0"
44+
45+
- name: Build
46+
run: |
47+
source ~/emsdk/emsdk_env.sh
48+
cmake --build build --target core_tests pke_tests -j2
49+
50+
- name: core_tests
51+
working-directory: build/unittest
52+
run: node --max-old-space-size=8192 ./core_tests.js --gtest_color=no
53+
54+
# Split pke_tests across two processes — each stays well below the 4 GB WASM cap.
55+
- name: pke_tests (CKKS + general)
56+
working-directory: build/unittest
57+
run: node --max-old-space-size=8192 ./pke_tests.js --gtest_filter='UTCKKSRNS*:UTCKKSCacheClear*:UTGENERAL*' --gtest_color=no
58+
59+
- name: pke_tests (BFV + BGV + binfhe)
60+
working-directory: build/unittest
61+
run: node --max-old-space-size=8192 ./pke_tests.js --gtest_filter='UTBFV*:UTBGV*:UTBINFHE*' --gtest_color=no

src/core/lib/utils/memory.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030
//==================================================================================
3131
#include "utils/memory.h"
3232

33-
#if defined(__GLIBC__)
33+
#if defined(__APPLE__)
34+
#include <mach/mach.h>
35+
#include <malloc/malloc.h>
36+
#elif defined(__GLIBC__)
37+
#include <malloc.h>
38+
#elif defined(_MSC_VER)
3439
#include <malloc.h>
3540
#endif
3641

@@ -67,9 +72,23 @@ namespace {
6772
// Not async-signal-safe and acquires allocator locks; call only from normal execution context,
6873
// never from a signal handler. Returns true if a trim was attempted, false on unsupported builds.
6974
bool AllocTrim() {
70-
#if defined(__GLIBC__)
75+
#if defined(__APPLE__)
76+
vm_address_t* zones = nullptr;
77+
unsigned count = 0;
78+
if (malloc_get_all_zones(mach_task_self(), nullptr, &zones, &count) == KERN_SUCCESS && zones) {
79+
for (unsigned i = 0; i < count; ++i) {
80+
auto* z = reinterpret_cast<malloc_zone_t*>(zones[i]);
81+
if (z)
82+
malloc_zone_pressure_relief(z, 0);
83+
}
84+
}
85+
return true;
86+
#elif defined(__GLIBC__)
7187
malloc_trim(0);
7288
return true;
89+
#elif defined(_MSC_VER)
90+
_heapmin();
91+
return true;
7392
#else
7493
return false;
7594
#endif

src/pke/include/cryptocontext.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -626,16 +626,24 @@ class CryptoContextImpl : public Serializable {
626626
* @brief Clear CKKS bootstrap precomputations cached by this context's scheme.
627627
*/
628628
void ClearBootstrapPrecom() noexcept {
629-
VerifyCKKSScheme(__func__);
630-
m_scheme->ClearBootstrapPrecom();
629+
if (m_scheme)
630+
m_scheme->ClearBootstrapPrecom();
631631
}
632632

633633
/**
634634
* @brief Clear CKKS/FHEW scheme-switch precomputations cached by this context's scheme.
635635
*/
636636
void ClearSchemeSwitchPrecom() noexcept {
637-
VerifyCKKSScheme(__func__);
638-
m_scheme->ClearSchemeSwitchPrecom();
637+
if (m_scheme)
638+
m_scheme->ClearSchemeSwitchPrecom();
639+
}
640+
641+
/**
642+
* @brief Release all scheme-level caches (bootstrap + scheme-switch).
643+
*/
644+
void ClearAllCKKSCaches() noexcept {
645+
ClearBootstrapPrecom();
646+
ClearSchemeSwitchPrecom();
639647
}
640648

641649
/**

src/pke/include/cryptocontextfactory.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ class CryptoContextFactory {
6464

6565
public:
6666
static void ReleaseAllContexts() {
67+
for (auto& cc : AllContexts) {
68+
if (cc)
69+
cc->ClearAllCKKSCaches();
70+
}
6771
if (!AllContexts.empty())
6872
AllContexts[0]->ClearStaticMapsAndVectors();
6973
AllContexts.clear();

src/pke/include/schemebase/base-scheme.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,13 +1147,13 @@ class SchemeBase {
11471147
}
11481148

11491149
void ClearBootstrapPrecom() noexcept {
1150-
VerifyFHEEnabled(__func__);
1151-
m_FHE->ClearBootstrapPrecom();
1150+
if (m_FHE)
1151+
m_FHE->ClearBootstrapPrecom();
11521152
}
11531153

11541154
void ClearSchemeSwitchPrecom() noexcept {
1155-
VerifySchemeSwitchEnabled(__func__);
1156-
m_SchemeSwitch->ClearSchemeSwitchPrecom();
1155+
if (m_SchemeSwitch)
1156+
m_SchemeSwitch->ClearSchemeSwitchPrecom();
11571157
}
11581158

11591159
std::shared_ptr<std::map<uint32_t, EvalKey<Element>>> EvalBootstrapKeyGen(const PrivateKey<Element> privateKey,

src/pke/unittest/utckksrns/UnitTestCKKSCacheClear.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,13 @@
3030
//==================================================================================
3131

3232
#include "binfhecontext.h"
33+
#include "gtest/gtest.h"
3334
#include "openfhe.h"
3435
#include "scheme/ckksrns/ckksrns-fhe.h"
3536
#include "scheme/ckksrns/ckksrns-schemeswitching.h"
3637
#include "scheme/scheme-swch-params.h"
3738
#include "utils/memory.h"
3839

39-
#include "gtest/gtest.h"
40-
4140
#include <vector>
4241

4342
#if defined(__GLIBC__)
@@ -116,9 +115,13 @@ CryptoContext<DCRTPoly> MakeSchemeSwitchCC() {
116115

117116
class UTCKKSCacheClear : public ::testing::Test {
118117
protected:
119-
#if defined(WITH_TCM)
118+
#if defined(WITH_TCM) || defined(__EMSCRIPTEN__)
120119
void SetUp() override {
120+
#if defined(WITH_TCM)
121121
GTEST_SKIP() << "Heap usage checks are not stable with tcmalloc enabled";
122+
#else
123+
GTEST_SKIP() << "Heap probe unavailable under Emscripten";
124+
#endif
122125
}
123126
#endif
124127

src/pke/unittest/utckksrns/UnitTestCKKSrns.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,6 @@ static std::vector<TEST_CASE_UTCKKSRNS> testCases = {
398398
{ EVAL_FAST_ROTATION, "39", {CKKSRNS_SCHEME, RING_DIM, 7, DFLT, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, BV, FLEXIBLEAUTOEXT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, BATCH},
399399
{ EVAL_FAST_ROTATION, "40", {CKKSRNS_SCHEME, RING_DIM, 7, DFLT, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, HYBRID, FLEXIBLEAUTOEXT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, BATCH},
400400
#endif
401-
#if !defined(__EMSCRIPTEN__)
402401
{ EVAL_FAST_ROTATION, "41", {CKKSRNS_SCHEME, RING_DIM, 7, DFLT, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, BV, FIXEDMANUAL, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, RING_DIM_HALF},
403402
{ EVAL_FAST_ROTATION, "42", {CKKSRNS_SCHEME, RING_DIM, 7, DFLT, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, BV, FIXEDAUTO, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, RING_DIM_HALF},
404403
{ EVAL_FAST_ROTATION, "43", {CKKSRNS_SCHEME, RING_DIM, 7, DFLT, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, HYBRID, FIXEDMANUAL, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, RING_DIM_HALF},
@@ -527,7 +526,6 @@ static std::vector<TEST_CASE_UTCKKSRNS> testCases = {
527526
// TestType, Descr, Scheme, RDim, MultDepth, SModSize, DSize, BatchSz, SecKeyDist, MaxRelinSkDeg, FModSize, SecLvl, KSTech, ScalTech, LDigits, PtMod, StdDev, EvalAddCt, KSCt, MultTech, EncTech, PREMode
528527
{ SMALL_SCALING_MOD_SIZE, "01", {CKKSRNS_SCHEME, 32768, 19, 22, DFLT, DFLT, DFLT, DFLT, 23, DFLT, DFLT, FIXEDMANUAL, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, },
529528
{ SMALL_SCALING_MOD_SIZE, "02", {CKKSRNS_SCHEME, 32768, 16, 50, DFLT, DFLT, DFLT, DFLT, 50, HEStd_NotSet, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, },
530-
#endif
531529
// ==========================================
532530
// TestType, Descr, Scheme, RDim, MultDepth, SModSize, DSize, BatchSz, SecKeyDist, MaxRelinSkDeg, FModSize, SecLvl, KSTech, ScalTech, LDigits, PtMod, StdDev, EvalAddCt, KSCt, MultTech, EncTech, PREMode, MultipartyMode, decryptionNoiseMode, ExecutionMode, NoiseEstimate, RegisterWordSize, compositeDegree, CKKSDataType
533531
{ EVALCOMPLEX, "01", {CKKSRNS_SCHEME, RING_DIM, 7, DFLT, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, BV, FIXEDMANUAL, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, COMPLEX}, },

src/pke/unittest/utckksrns/UnitTestCKKSrnsSerialize.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ constexpr uint32_t BATCH = 16;
119119
// clang-format off
120120
static std::vector<TEST_CASE_UTCKKSRNS_SER> testCases = {
121121
// TestType, Descr, Scheme, RDim, MultDepth, SModSize, DSize, BatchSz, SecKeyDist, MaxRelinSkDeg, FModSize, SecLvl, KSTech, ScalTech, LDigits, PtMod, StdDev, EvalAddCt, KSCt, MultTech, EncTech, PREMode
122-
#if !defined(__EMSCRIPTEN__)
123122
{ CONTEXT_WITH_SERTYPE, "01", {CKKSRNS_SCHEME, RING_DIM, MULT_DEPTH, SMODSIZE, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, BV, FIXEDMANUAL, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, },
124123
{ CONTEXT_WITH_SERTYPE, "02", {CKKSRNS_SCHEME, RING_DIM, MULT_DEPTH, SMODSIZE, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, BV, FIXEDAUTO, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, },
125124
{ CONTEXT_WITH_SERTYPE, "03", {CKKSRNS_SCHEME, RING_DIM, MULT_DEPTH, SMODSIZE, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, HYBRID, FIXEDMANUAL, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, },
@@ -129,7 +128,6 @@ static std::vector<TEST_CASE_UTCKKSRNS_SER> testCases = {
129128
{ CONTEXT_WITH_SERTYPE, "06", {CKKSRNS_SCHEME, RING_DIM, MULT_DEPTH, SMODSIZE, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, HYBRID, FLEXIBLEAUTO, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, },
130129
{ CONTEXT_WITH_SERTYPE, "07", {CKKSRNS_SCHEME, RING_DIM, MULT_DEPTH, SMODSIZE, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, BV, FLEXIBLEAUTOEXT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, },
131130
{ CONTEXT_WITH_SERTYPE, "08", {CKKSRNS_SCHEME, RING_DIM, MULT_DEPTH, SMODSIZE, DSIZE, BATCH, DFLT, DFLT, DFLT, HEStd_NotSet, HYBRID, FLEXIBLEAUTOEXT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT, DFLT}, },
132-
#endif
133131
#endif
134132
// ==========================================
135133
// TestType, Descr, Scheme, RDim, MultDepth, SModSize, DSize, BatchSz, SecKeyDist, MaxRelinSkDeg, FModSize, SecLvl, KSTech, ScalTech, LDigits, PtMod, StdDev, EvalAddCt, KSCt, MultTech, EncTech, PREMode
@@ -189,6 +187,7 @@ class UTCKKSRNS_SER : public ::testing::TestWithParam<TEST_CASE_UTCKKSRNS_SER> {
189187
CryptoContext<Element> cc(UnitTestGenerateContext(testData.params));
190188

191189
UnitTestContextWithSertype(cc, SerType::JSON, "json");
190+
cc->ClearAllCKKSCaches();
192191
UnitTestContextWithSertype(cc, SerType::BINARY, "binary");
193192
}
194193

0 commit comments

Comments
 (0)