Skip to content

Commit 34ea2c8

Browse files
committed
src: compress embedded icu, builtins, and snapshot
Store the full ICU data file, one-byte builtin sources, and the V8 startup snapshot plus code caches as zstd frames instead of raw bytes. Decompress each once at startup and keep it for the process lifetime. On macOS Release, link the executable with -dead_strip, -x, and -S. A Release arm64 macOS binary dropped from 140 MB to 59 MB. Intl, builtins, the snapshot, and code cache behavior are unchanged. Startup RSS rose by about 40 MB because those blobs are private heap instead of file-backed pages. Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 67a4416 commit 34ea2c8

11 files changed

Lines changed: 595 additions & 100 deletions

File tree

‎deps/zstd/zstd.gyp‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,16 @@
100100
],
101101
'sources': [
102102
'<@(zstd_sources)',
103-
]
104-
}
103+
],
104+
'toolsets': ['host', 'target'],
105+
},
106+
{
107+
'target_name': 'zstd_compress',
108+
'type': 'executable',
109+
'toolsets': ['host'],
110+
'dependencies': ['zstd#host'],
111+
'include_dirs': ['lib'],
112+
'sources': ['../../tools/zstd_compress.cc'],
113+
},
105114
]
106115
}

‎node.gyp‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@
185185
'src/node_watchdog.cc',
186186
'src/node_worker.cc',
187187
'src/node_zlib.cc',
188+
'src/zstd_blob.cc',
189+
'src/zstd_blob.h',
188190
'src/path.cc',
189191
'src/permission/fs_permission.cc',
190192
'src/permission/permission.cc',
@@ -659,6 +661,23 @@
659661
'WARNING_CFLAGS': [ '-Werror' ],
660662
},
661663
}],
664+
# The Release executable force-loads several static libraries, so
665+
# unreferenced objects and the symbol table dominate the file.
666+
# -dead_strip drops unreferenced .o files, -x drops local symbols,
667+
# and -S drops STABS. Debug keeps symbols.
668+
['OS=="mac" or OS=="ios"', {
669+
'configurations': {
670+
'Release': {
671+
'xcode_settings': {
672+
'OTHER_LDFLAGS': [
673+
'-Wl,-dead_strip',
674+
'-Wl,-x',
675+
'-Wl,-S',
676+
],
677+
},
678+
},
679+
},
680+
}],
662681
['node_shared=="true" and OS=="win"', {
663682
'dependencies': ['generate_node_def'],
664683
'msvs_settings': {
@@ -1671,6 +1690,9 @@
16711690
[ 'node_shared_libuv=="false"', {
16721691
'dependencies': [ 'deps/uv/uv.gyp:libuv#host' ],
16731692
}],
1693+
[ 'node_shared_zstd=="false"', {
1694+
'dependencies': [ 'deps/zstd/zstd.gyp:zstd#host' ],
1695+
}],
16741696
[ 'OS in "linux mac openharmony"', {
16751697
'defines': ['NODE_JS2C_USE_STRING_LITERALS'],
16761698
}],

‎node.gypi‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@
135135
'<(icu_gyp_path):icuuc',
136136
],
137137
'conditions': [
138+
[ 'icu_system!="true"', {
139+
'defines': [ 'NODE_HAVE_EMBEDDED_ICU_ZSTD=1' ],
140+
}],
138141
[ 'icu_small=="true"', {
139142
'defines': [ 'NODE_HAVE_SMALL_ICU=1' ],
140143
'conditions': [
@@ -144,7 +147,8 @@
144147
],
145148
}],
146149
],
147-
}]],
150+
}],
151+
],
148152
}],
149153
[ 'node_use_bundled_v8=="true" and \
150154
node_enable_v8_vtunejit=="true" and (target_arch=="x64" or \

‎src/node_i18n.cc‎

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
#include "node_i18n.h"
4444
#include "node_external_reference.h"
4545
#include "simdutf.h"
46+
#include "zstd_blob.h"
47+
48+
#include <cstdint>
49+
#include <cstring>
4650

4751
#if defined(NODE_HAVE_I18N_SUPPORT)
4852

@@ -68,7 +72,7 @@
6872
#include <unicode/uversion.h>
6973
#include "nbytes.h"
7074

71-
#ifdef NODE_HAVE_SMALL_ICU
75+
#if defined(NODE_HAVE_SMALL_ICU) || defined(NODE_HAVE_EMBEDDED_ICU_ZSTD)
7276
#include <unicode/udata.h>
7377

7478
/* if this is defined, we have a 'secondary' entry point.
@@ -85,6 +89,38 @@
8589
extern "C" const char U_DATA_API SMALL_ICUDATA_ENTRY_POINT[];
8690
#endif
8791

92+
#ifdef NODE_HAVE_EMBEDDED_ICU_ZSTD
93+
extern "C" const uint8_t node_icu_zstd_dat[];
94+
95+
static uint64_t ReadU64LE(const uint8_t* bytes) {
96+
uint64_t value = 0;
97+
for (int i = 0; i < 8; i++) {
98+
value |= static_cast<uint64_t>(bytes[i]) << (8 * i);
99+
}
100+
return value;
101+
}
102+
103+
// Decompress the embedded ICU data file. The returned pointer is aligned and
104+
// lives for the process lifetime. nullptr on failure, with `error` set.
105+
static uint8_t* DecompressEmbeddedICU(std::string* error) {
106+
const uint8_t* bytes = node_icu_zstd_dat;
107+
if (memcmp(bytes, "ICUZ", 4) != 0) {
108+
*error = "embedded ICU data header is invalid";
109+
return nullptr;
110+
}
111+
uint64_t raw_size = ReadU64LE(bytes + 4);
112+
uint64_t compressed_size = ReadU64LE(bytes + 12);
113+
size_t got = 0;
114+
uint8_t* data = node::ZstdDecompressAligned(
115+
bytes + 20, static_cast<size_t>(compressed_size), &got);
116+
if (data == nullptr || got != raw_size) {
117+
*error = "failed to decompress embedded ICU data";
118+
return nullptr;
119+
}
120+
return data;
121+
}
122+
#endif // NODE_HAVE_EMBEDDED_ICU_ZSTD
123+
88124
namespace node {
89125

90126
using v8::Context;
@@ -555,7 +591,13 @@ ConverterObject::ConverterObject(
555591
bool InitializeICUDirectory(const std::string& path, std::string* error) {
556592
UErrorCode status = U_ZERO_ERROR;
557593
if (path.empty()) {
558-
#ifdef NODE_HAVE_SMALL_ICU
594+
#ifdef NODE_HAVE_EMBEDDED_ICU_ZSTD
595+
static uint8_t* icu_data = DecompressEmbeddedICU(error);
596+
if (icu_data == nullptr) {
597+
return false;
598+
}
599+
udata_setCommonData(icu_data, &status);
600+
#elif defined(NODE_HAVE_SMALL_ICU)
559601
// install the 'small' data.
560602
udata_setCommonData(&SMALL_ICUDATA_ENTRY_POINT, &status);
561603
#else // !NODE_HAVE_SMALL_ICU

0 commit comments

Comments
 (0)