Skip to content

Commit 0e2f37b

Browse files
xal-0KristofferC
authored andcommitted
Remove libjulia-internal.so dependency on libstdc++ and libgcc (#60248)
Some testing by @BenChung has revealed how hard it is to load a JuliaC-built library into a program that has already loaded a very old version of libstdc++. Even with probing disabled, `libjulia-internal.so` fails because of missing GLIBCXX symbols. We use so little of the C++ standard library in `libjulia-internal.so` that it's worth the tradeoff to link it statically: it barely changes the size of the resulting library, removes a medium-size library we have to ship in trimmed bundles, and solves some of our hermeticity issues when being loaded by other software. `libjulia-codegen.so` uses it more extensively, and we expect to be able to load it as a plugin for `opt`, meaning it may have to remain dynamically linked. This PR contains a series of changes to enable statically linked libstdc++ by default and mitigate the size impact: - We enable `--gc-sections` when building with gcc/ld.bfd. This saves us some code already, since we can trim out a lot of libLLVMSupport/libLLVMTargetParser. On macOS, we can use `-dead_strip` to save some space even though the rest of these changes are not applicable. - Two flags for `-static-libstdc++` and `-static-libgcc`, called `USE_RT_STATIC_LIBSTDCXX` and `USE_RT_STATIC_LIBGCC`, are added and enabled by default. - Most of the additional code that gets linked into `libjulia-internal.so` is related to locales for iostreams. Replace these with standard C IO and LLVM helpers that don't have weird locale-related behaviour. - (NOT IMPLEMENTED) `--gc-sections` removes unused executable code, but leaves us with a lot of irrelevant debug info. I tested the effect of `llvm-dwarfutil --garbage-collection` and saw pretty good savings, but that is not included in this PR because BinaryBuilder doesn't have a new enough version of the LLVM tools. | Change | Size of `libjulia-internal.so` (KiB) | |--------------------------------------|--------------------------------------| | No change | 14524 | | Linker GC enabled | 13220 | | -static-libstdc++ and -static-libgcc | 22136 | | Excise iostreams | 15036 | | DWARF GC (not in this PR) | 11488 | This is a comparison of symbols that were added and removed. Even though 15 times more code is removed than added, the resulting `libjulia-internal.so` has a similar size to the original because of the additional debug info. (cherry picked from commit f36882f)
1 parent 219561a commit 0e2f37b

File tree

8 files changed

+79
-57
lines changed

8 files changed

+79
-57
lines changed

Make.inc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ USE_SYSTEM_ZSTD:=0
6363
USE_SYSTEM_P7ZIP:=0
6464
USE_SYSTEM_LLD:=0
6565

66+
# Link libjulia-internal with static libgcc and libstdc++
67+
USE_RT_STATIC_LIBGCC:=1
68+
USE_RT_STATIC_LIBSTDCXX:=1
69+
6670
# Link to the LLVM shared library
6771
USE_LLVM_SHLIB := 1
6872

@@ -97,6 +101,9 @@ WITH_TRACY_CALLSTACKS := 0
97101
# Enable Timing Counts support
98102
WITH_TIMING_COUNTS := 0
99103

104+
# Should --gc-sections/-dead_strip be used to remove unreferenced code?
105+
USE_LINKER_GC:=1
106+
100107
# Prevent picking up $ARCH from the environment variables
101108
ARCH:=
102109

@@ -932,6 +939,23 @@ JCXXFLAGS += -DUSE_NVTX
932939
JCFLAGS += -DUSE_NVTX
933940
endif
934941

942+
ifneq ($(findstring $(OS),WINNT FreeBSD OpenBSD),)
943+
USE_LINKER_GC := 0
944+
USE_RT_STATIC_LIBGCC := 0
945+
USE_RT_STATIC_LIBSTDCXX := 0
946+
endif
947+
948+
# Linker garbage collection
949+
ifeq ($(USE_LINKER_GC), 1)
950+
ifeq ($(OS), Darwin)
951+
JLDFLAGS += -Wl,-dead_strip
952+
else
953+
JLDFLAGS += -Wl,--gc-sections
954+
JCFLAGS += -ffunction-sections -fdata-sections
955+
JCXXFLAGS += -ffunction-sections -fdata-sections
956+
endif
957+
endif
958+
935959
# ===========================================================================
936960

937961
# Select the cpu architecture to target, or automatically detects the user's compiler

cli/Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ $(BUILDDIR)/loader_lib.o: export MSYS2_ARG_CONV_EXCL = -DDEP_LIBS=
3939
$(BUILDDIR)/loader_lib.dbg.obj: export MSYS2_ARG_CONV_EXCL = -DDEP_LIBS=
4040
endif # MSYS2
4141

42+
ifeq ($(USE_RT_STATIC_LIBSTDCXX),1)
43+
SHIPFLAGS += -DRT_STATIC_LIBSTDCXX
44+
DEBUGFLAGS += -DRT_STATIC_LIBSTDCXX
45+
endif # USE_RT_STATIC_LIBSTDCXX
46+
4247
EXE_OBJS := $(BUILDDIR)/loader_exe.o
4348
EXE_DOBJS := $(BUILDDIR)/loader_exe.dbg.obj
4449
LIB_OBJS := $(BUILDDIR)/loader_lib.o

cli/loader_lib.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,13 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
497497
// If the probe rejected the system libstdc++ (or didn't find one!)
498498
// just load our bundled libstdc++ as identified by curr_dep;
499499
if (!probe_successful) {
500+
# ifdef RT_STATIC_LIBSTDCXX
501+
// If we have a statically-linked libstdc++, it is ok for
502+
// this to fail.
503+
load_library(curr_dep, lib_dir, 0);
504+
# else
500505
load_library(curr_dep, lib_dir, 1);
506+
# endif
501507
}
502508
#endif
503509
} else if (special_idx == 1) {

src/Makefile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ RT_LIBS := $(WHOLE_ARCHIVE) $(LIBUV) $(WHOLE_ARCHIVE) $(LIBUTF8PROC) $(NO_WHOLE_
207207
# NB: CG needs uv_mutex_* symbols, but we expect to export them from libjulia-internal
208208
CG_LIBS := $(LIBUNWIND) $(CG_LLVMLINK) $(OSLIBS) $(LIBTRACYCLIENT) $(LIBITTAPI)
209209

210+
ifeq ($(USEGCC),1)
211+
ifeq ($(USE_RT_STATIC_LIBGCC),1)
212+
RT_LIBS += -static-libgcc
213+
endif
214+
ifeq ($(USE_RT_STATIC_LIBSTDCXX),1)
215+
RT_LIBS += -static-libstdc++
216+
endif
217+
endif
218+
210219
ifeq (${USE_THIRD_PARTY_GC},mmtk)
211220
RT_LIBS += $(MMTK_LIB)
212221
CG_LIBS += $(MMTK_LIB)

src/coverage.cpp

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
#include <cstdint>
44
#include <pthread.h>
55
#include <string>
6-
#include <fstream>
7-
#include <map>
8-
#include <vector>
96

107
#include "llvm-version.h"
118
#include <llvm/ADT/StringRef.h>
@@ -137,26 +134,20 @@ static void write_log_data(logdata_t &logData, const char *extension) JL_NOTSAFE
137134
if (!values.empty()) {
138135
if (!jl_isabspath(filename.c_str()))
139136
filename = base + filename;
140-
std::ifstream inf(filename.c_str());
141-
if (!inf.is_open())
137+
FILE *inf = fopen(filename.c_str(), "r");
138+
if (!inf)
142139
continue;
143140
std::string outfile = filename + extension;
144-
std::ofstream outf(outfile.c_str(), std::ofstream::trunc | std::ofstream::out | std::ofstream::binary);
145-
if (outf.is_open()) {
146-
inf.exceptions(std::ifstream::badbit);
147-
outf.exceptions(std::ifstream::failbit | std::ifstream::badbit);
141+
FILE *outf = fopen(outfile.c_str(), "wb");
142+
if (outf) {
148143
char line[1024];
149144
int l = 1;
150145
unsigned block = 0;
151-
while (!inf.eof()) {
152-
inf.getline(line, sizeof(line));
153-
if (inf.fail()) {
154-
if (inf.eof())
155-
break; // no content on trailing line
156-
// Read through lines longer than sizeof(line)
157-
inf.clear();
158-
inf.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
159-
}
146+
int ret = 0;
147+
while (ret != EOF && (ret = fscanf(inf, "%1023[^\n]", line)) != EOF) {
148+
// Skip n non-newline chars and a single trailing newline
149+
if ((ret = fscanf(inf, "%*[^\n]")) != EOF)
150+
ret = fscanf(inf, "%*1[\n]");
160151
logdata_block *data = NULL;
161152
if (block < values.size()) {
162153
data = values[block];
@@ -166,32 +157,32 @@ static void write_log_data(logdata_t &logData, const char *extension) JL_NOTSAFE
166157
l = 0;
167158
block++;
168159
}
169-
outf.width(9);
170160
if (value == 0)
171-
outf << '-';
161+
fprintf(outf, " -");
172162
else
173-
outf << (value - 1);
174-
outf.width(0);
175-
outf << " " << line << '\n';
163+
fprintf(outf, "%9" PRIu64, value - 1);
164+
fprintf(outf, " %s\n", line);
165+
line[0] = 0;
176166
}
177-
outf.close();
167+
fclose(outf);
178168
}
179-
inf.close();
169+
fclose(inf);
180170
}
181171
}
182172
}
183173

184174
static void write_lcov_data(logdata_t &logData, const std::string &outfile) JL_NOTSAFEPOINT
185175
{
186-
std::ofstream outf(outfile.c_str(), std::ofstream::ate | std::ofstream::out | std::ofstream::binary);
176+
FILE *outf = fopen(outfile.c_str(), "ab");
177+
if (!outf) return;
187178
//std::string base = std::string(jl_options.julia_bindir);
188179
//base = base + "/../share/julia/base/";
189180
logdata_t::iterator it = logData.begin();
190181
for (; it != logData.end(); it++) {
191182
StringRef filename = it->first();
192183
const SmallVector<logdata_block*, 0> &values = it->second;
193184
if (!values.empty()) {
194-
outf << "SF:" << filename.str() << '\n';
185+
fprintf(outf, "SF:%.*s\n", (int)filename.size(), filename.data());
195186
size_t n_covered = 0;
196187
size_t n_instrumented = 0;
197188
size_t lno = 0;
@@ -204,7 +195,7 @@ static void write_lcov_data(logdata_t &logData, const std::string &outfile) JL_N
204195
n_instrumented++;
205196
if (cov > 1)
206197
n_covered++;
207-
outf << "DA:" << lno << ',' << (cov - 1) << '\n';
198+
fprintf(outf, "DA:%zu,%" PRIu64 "\n", lno, cov - 1);
208199
}
209200
lno++;
210201
}
@@ -213,12 +204,12 @@ static void write_lcov_data(logdata_t &logData, const std::string &outfile) JL_N
213204
lno += logdata_blocksize;
214205
}
215206
}
216-
outf << "LH:" << n_covered << '\n';
217-
outf << "LF:" << n_instrumented << '\n';
218-
outf << "end_of_record\n";
207+
fprintf(outf, "LH:%zu\n", n_covered);
208+
fprintf(outf, "LF:%zu\n", n_instrumented);
209+
fprintf(outf, "end_of_record\n");
219210
}
220211
}
221-
outf.close();
212+
fclose(outf);
222213
}
223214

224215
extern "C" JL_DLLEXPORT void jl_write_coverage_data(const char *output) JL_NOTSAFEPOINT

src/gc-heap-snapshot.cpp

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,19 @@
66
#include "julia_internal.h"
77
#include "julia_assert.h"
88

9+
#include "llvm/ADT/DenseMap.h"
10+
#include "llvm/ADT/SmallString.h"
911
#include "llvm/ADT/SmallVector.h"
1012
#include "llvm/ADT/StringMap.h"
11-
#include "llvm/ADT/DenseMap.h"
12-
13-
#include <vector>
14-
#include <string>
15-
#include <sstream>
16-
#include <iostream>
17-
#include <set>
13+
#include "llvm/Support/FormatVariadic.h"
1814

19-
using std::string;
20-
using std::set;
21-
using std::ostringstream;
22-
using std::pair;
2315
using std::make_pair;
2416
using llvm::SmallVector;
2517
using llvm::StringMap;
2618
using llvm::DenseMap;
2719
using llvm::StringRef;
20+
using llvm::SmallString;
21+
using llvm::formatv;
2822

2923
// https://stackoverflow.com/a/33799784/751061
3024
void print_str_escape_json(ios_t *stream, StringRef s)
@@ -422,18 +416,16 @@ static size_t record_pointer_to_gc_snapshot(void *a, size_t bytes, StringRef nam
422416
return val.first->second;
423417
}
424418

425-
static string _fieldpath_for_slot(void *obj, void *slot) JL_NOTSAFEPOINT
419+
static SmallString<128> _fieldpath_for_slot(void *obj, void *slot) JL_NOTSAFEPOINT
426420
{
427-
string res;
421+
SmallString<128> res;
428422
jl_datatype_t *objtype = (jl_datatype_t*)jl_typeof(obj);
429423

430424
while (1) {
431425
int i = gc_slot_to_fieldidx(obj, slot, objtype);
432426

433427
if (jl_is_tuple_type(objtype) || jl_is_namedtuple_type(objtype)) {
434-
ostringstream ss;
435-
ss << "[" << i << "]";
436-
res += ss.str();
428+
res += formatv("[{0}]", i).sstr<8>();
437429
}
438430
else {
439431
jl_svec_t *field_names = jl_field_names(objtype);
@@ -472,9 +464,8 @@ void _gc_heap_snapshot_record_gc_roots(jl_value_t *root, char *name) JL_NOTSAFEP
472464
void _gc_heap_snapshot_record_finlist(jl_value_t *obj, size_t index) JL_NOTSAFEPOINT
473465
{
474466
auto to_node_idx = record_node_to_gc_snapshot(obj);
475-
ostringstream ss;
476-
ss << "finlist-" << index;
477-
auto edge_label = g_snapshot->names.serialize_if_necessary(g_snapshot->strings, ss.str());
467+
SmallString<16> ss = formatv("finlist-{0}", index);
468+
auto edge_label = g_snapshot->names.serialize_if_necessary(g_snapshot->strings, ss);
478469
_record_gc_just_edge("internal", g_snapshot->_gc_finlist_root_idx, to_node_idx, edge_label);
479470
}
480471

@@ -536,7 +527,7 @@ void _gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t *to, size_
536527

537528
void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void *slot) JL_NOTSAFEPOINT
538529
{
539-
string path = _fieldpath_for_slot(from, slot);
530+
SmallString<128> path = _fieldpath_for_slot(from, slot);
540531
_record_gc_edge("property", from, to,
541532
g_snapshot->names.serialize_if_necessary(g_snapshot->strings, path));
542533
}

src/processor.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#include "julia.h"
1717
#include "julia_internal.h"
1818

19-
#include <map>
2019
#include <algorithm>
2120

2221
#include "julia_assert.h"
@@ -25,8 +24,6 @@
2524
#include <dlfcn.h>
2625
#endif
2726

28-
#include <iostream>
29-
3027
// CPU target string is a list of strings separated by `;` each string starts with a CPU
3128
// or architecture name and followed by an optional list of features separated by `,`.
3229
// A "generic" or empty CPU name means the basic required feature set of the target ISA

src/runtime_ccall.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// This file is a part of Julia. License is MIT: https://julialang.org/license
22

33
#include "llvm-version.h"
4-
#include <map>
54
#include <string>
65
#include <llvm/ADT/StringMap.h>
76
#include <llvm/TargetParser/Host.h>
@@ -25,7 +24,7 @@ using namespace llvm;
2524
jl_value_t *jl_libdl_dlopen_func JL_GLOBALLY_ROOTED;
2625

2726
// map from user-specified lib names to handles
28-
static std::map<std::string, void*> libMap;
27+
static StringMap<void*> libMap;
2928
static jl_mutex_t libmap_lock;
3029
extern "C"
3130
void *jl_get_library_(const char *f_lib, int throw_err)

0 commit comments

Comments
 (0)