Skip to content
Closed
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
61 changes: 61 additions & 0 deletions .github/workflows/emscripten.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Emscripten

# Builds with emcc and runs the WASM unit tests under node. Guards against
# re-introducing the cache-leak class that issue #249 tracked.

on:
pull_request:
branches: [dev]
push:
branches: [dev, main]
workflow_dispatch:

jobs:
build-and-test:
runs-on: ubuntu-22.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with: { node-version: '20' }

- name: Cache emsdk
id: cache-emsdk
uses: actions/cache@v4
with:
path: ~/emsdk
key: emsdk-3.1.59

- name: Install emsdk
if: steps.cache-emsdk.outputs.cache-hit != 'true'
run: |
git clone --depth 1 --branch 3.1.59 https://github.com/emscripten-core/emsdk.git ~/emsdk
~/emsdk/emsdk install 3.1.59
~/emsdk/emsdk activate 3.1.59

- name: Configure
run: |
source ~/emsdk/emsdk_env.sh
emcmake cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release -DBUILD_UNITTESTS=ON \
-DBUILD_BENCHMARKS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_EXTRAS=OFF \
-DBUILD_STATIC=ON -DBUILD_SHARED=OFF -DWITH_OPENMP=OFF -DNATIVE_SIZE=64 \
-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"

- name: Build
run: |
source ~/emsdk/emsdk_env.sh
cmake --build build --target core_tests pke_tests -j2

- name: core_tests
working-directory: build/unittest
run: node --max-old-space-size=8192 ./core_tests.js --gtest_color=no

# Split pke_tests across two processes — each stays well below the 4 GB WASM cap.
- name: pke_tests (CKKS + general)
working-directory: build/unittest
run: node --max-old-space-size=8192 ./pke_tests.js --gtest_filter='UTCKKSRNS*:UTCKKSCacheClear*:UTGENERAL*' --gtest_color=no

- name: pke_tests (BFV + BGV + binfhe)
working-directory: build/unittest
run: node --max-old-space-size=8192 ./pke_tests.js --gtest_filter='UTBFV*:UTBGV*:UTBINFHE*' --gtest_color=no
4 changes: 4 additions & 0 deletions src/core/include/utils/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ void MoveAppend(std::vector<X>& dst, std::vector<X>& src) {
*/
void secure_memset(volatile void* mem, uint8_t c, size_t len);

/// Trim/purge the C allocator so freed pages return to the OS. Returns true if
/// the platform has a trim primitive, false (no-op) otherwise.
bool TrimAllocator();

} // namespace lbcrypto

#endif // LBCRYPTO_UTILS_MEMORY_H
32 changes: 32 additions & 0 deletions src/core/lib/utils/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
//==================================================================================
#include "utils/memory.h"

#if defined(__APPLE__)
#include <mach/mach.h>
#include <malloc/malloc.h>
#elif defined(__GLIBC__)
#include <malloc.h>
#elif defined(_MSC_VER)
#include <malloc.h>
#endif

namespace lbcrypto {

void secure_memset(volatile void* mem, uint8_t c, size_t len) {
Expand All @@ -38,4 +47,27 @@ void secure_memset(volatile void* mem, uint8_t c, size_t len) {
*(ptr + i) = c;
}

bool TrimAllocator() {
#if defined(__GLIBC__)
malloc_trim(0);
return true;
#elif defined(__APPLE__)
vm_address_t* zones = nullptr;
unsigned count = 0;
if (malloc_get_all_zones(mach_task_self(), nullptr, &zones, &count) == KERN_SUCCESS && zones) {
for (unsigned i = 0; i < count; ++i) {
auto* z = reinterpret_cast<malloc_zone_t*>(zones[i]);
if (z)
malloc_zone_pressure_relief(z, 0);
}
}
return true;
#elif defined(_MSC_VER)
_heapmin();
return true;
#else
return false;
#endif
}

} // namespace lbcrypto
22 changes: 12 additions & 10 deletions src/pke/include/cryptocontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -622,20 +622,22 @@ class CryptoContextImpl : public Serializable {
*/
static void ClearStaticMapsAndVectors();

/**
* @brief Clear CKKS bootstrap precomputations cached by this context's scheme.
*/
/// Clear CKKS bootstrap precomputations. Safe on any scheme.
void ClearBootstrapPrecom() noexcept {
VerifyCKKSScheme(__func__);
m_scheme->ClearBootstrapPrecom();
if (m_scheme)
m_scheme->ClearBootstrapPrecom();
}

/**
* @brief Clear CKKS/FHEW scheme-switch precomputations cached by this context's scheme.
*/
/// Clear CKKS/FHEW scheme-switch precomputations. Safe on any scheme.
void ClearSchemeSwitchPrecom() noexcept {
VerifyCKKSScheme(__func__);
m_scheme->ClearSchemeSwitchPrecom();
if (m_scheme)
m_scheme->ClearSchemeSwitchPrecom();
}

/// Release all scheme-level caches (bootstrap + scheme-switch).
void ClearAllCKKSCaches() noexcept {
ClearBootstrapPrecom();
ClearSchemeSwitchPrecom();
}

/**
Expand Down
16 changes: 15 additions & 1 deletion src/pke/include/cryptocontextfactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "cryptocontext-fwd.h"
#include "lattice/lat-hal.h"
#include "scheme/scheme-id.h"
#include "utils/memory.h"

#include <memory>
#include <string>
Expand Down Expand Up @@ -63,12 +64,25 @@ class CryptoContextFactory {

public:
static void ReleaseAllContexts() {
if (!AllContexts.empty())
// Drop scheme-level caches on every live context so memory held by
// m_bootPrecomMap / scheme-switch state is freed even if callers
// still hold a CryptoContext (issue #533).
for (auto& cc : AllContexts) {
if (cc)
cc->ClearAllCKKSCaches();
}
if (AllContexts.size() > 0)
AllContexts[0]->ClearStaticMapsAndVectors();

AllContexts.clear();
}

/// Like ReleaseAllContexts() but also trims the C allocator so RSS drops.
static void ReleaseAllContextsAndTrim() {
ReleaseAllContexts();
TrimAllocator();
}

static int GetContextCount() {
return AllContexts.size();
}
Expand Down
8 changes: 4 additions & 4 deletions src/pke/include/schemebase/base-scheme.h
Original file line number Diff line number Diff line change
Expand Up @@ -1147,13 +1147,13 @@ class SchemeBase {
}

void ClearBootstrapPrecom() noexcept {
VerifyFHEEnabled(__func__);
m_FHE->ClearBootstrapPrecom();
if (m_FHE)
m_FHE->ClearBootstrapPrecom();
}

void ClearSchemeSwitchPrecom() noexcept {
VerifySchemeSwitchEnabled(__func__);
m_SchemeSwitch->ClearSchemeSwitchPrecom();
if (m_SchemeSwitch)
m_SchemeSwitch->ClearSchemeSwitchPrecom();
}

std::shared_ptr<std::map<uint32_t, EvalKey<Element>>> EvalBootstrapKeyGen(const PrivateKey<Element> privateKey,
Expand Down
6 changes: 5 additions & 1 deletion src/pke/unittest/utckksrns/UnitTestCKKSCacheClear.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,13 @@ CryptoContext<DCRTPoly> MakeSchemeSwitchCC() {

class UTCKKSCacheClear : public ::testing::Test {
protected:
#if defined(WITH_TCM)
#if defined(WITH_TCM) || defined(__EMSCRIPTEN__)
void SetUp() override {
#if defined(WITH_TCM)
GTEST_SKIP() << "Heap usage checks are not stable with tcmalloc enabled";
#else
GTEST_SKIP() << "Heap probe unavailable under Emscripten";
#endif
}
#endif

Expand Down
7 changes: 4 additions & 3 deletions src/pke/unittest/utckksrns/UnitTestCKKSrns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ static std::vector<TEST_CASE_UTCKKSRNS> testCases = {
{ 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},
{ 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},
#endif
#if !defined(__EMSCRIPTEN__)
// Issue #249: previously gated under !defined(__EMSCRIPTEN__) — re-enabled
// now that TearDown calls ReleaseAllContextsAndTrim (PR #1179).
{ 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},
{ 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},
{ 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},
Expand Down Expand Up @@ -527,7 +528,6 @@ static std::vector<TEST_CASE_UTCKKSRNS> testCases = {
// TestType, Descr, Scheme, RDim, MultDepth, SModSize, DSize, BatchSz, SecKeyDist, MaxRelinSkDeg, FModSize, SecLvl, KSTech, ScalTech, LDigits, PtMod, StdDev, EvalAddCt, KSCt, MultTech, EncTech, PREMode
{ 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}, },
{ 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}, },
#endif
// ==========================================
// 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
{ 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}, },
Expand Down Expand Up @@ -641,7 +641,8 @@ class UTCKKSRNS : public ::testing::TestWithParam<TEST_CASE_UTCKKSRNS> {
}

void TearDown() {
CryptoContextFactory<DCRTPoly>::ReleaseAllContexts();
// Trim variant keeps peak WASM linear memory under the 4 GB cap (issue #249).
CryptoContextFactory<DCRTPoly>::ReleaseAllContextsAndTrim();
OpenFHEParallelControls.UnitTestStop();
}

Expand Down
6 changes: 3 additions & 3 deletions src/pke/unittest/utckksrns/UnitTestCKKSrnsSerialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ constexpr uint32_t BATCH = 16;
// clang-format off
static std::vector<TEST_CASE_UTCKKSRNS_SER> testCases = {
// TestType, Descr, Scheme, RDim, MultDepth, SModSize, DSize, BatchSz, SecKeyDist, MaxRelinSkDeg, FModSize, SecLvl, KSTech, ScalTech, LDigits, PtMod, StdDev, EvalAddCt, KSCt, MultTech, EncTech, PREMode
#if !defined(__EMSCRIPTEN__)
// Issue #249: re-enabled — see matching note in UnitTestCKKSrns.cpp.
{ 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}, },
{ 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}, },
{ 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}, },
Expand All @@ -129,7 +129,6 @@ static std::vector<TEST_CASE_UTCKKSRNS_SER> testCases = {
{ 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}, },
{ 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}, },
{ 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}, },
#endif
#endif
// ==========================================
// TestType, Descr, Scheme, RDim, MultDepth, SModSize, DSize, BatchSz, SecKeyDist, MaxRelinSkDeg, FModSize, SecLvl, KSTech, ScalTech, LDigits, PtMod, StdDev, EvalAddCt, KSCt, MultTech, EncTech, PREMode
Expand Down Expand Up @@ -181,14 +180,15 @@ class UTCKKSRNS_SER : public ::testing::TestWithParam<TEST_CASE_UTCKKSRNS_SER> {
}

void TearDown() {
CryptoContextFactory<DCRTPoly>::ReleaseAllContexts();
CryptoContextFactory<DCRTPoly>::ReleaseAllContextsAndTrim(); // issue #249
OpenFHEParallelControls.UnitTestStop();
}

void UnitTestContext(const TEST_CASE_UTCKKSRNS_SER& testData, const std::string& failmsg = std::string()) {
CryptoContext<Element> cc(UnitTestGenerateContext(testData.params));

UnitTestContextWithSertype(cc, SerType::JSON, "json");
cc->ClearAllCKKSCaches(); // free caches between rounds (issue #249)
UnitTestContextWithSertype(cc, SerType::BINARY, "binary");
}

Expand Down
Loading