jetc: add --shared mode to compile templates to a dlopen()-able .so
For the server you're about to build: `jetc --shared template.jet out.cpp`
now emits no main() at all - instead the render logic is exported as
`extern "C" void jetc_render_<template>(jet::Global &global,
coreutils::MString &out)`, one call per request, meant to be dlsym()'d
by a long-running host process instead of spawning a jet-2.0 process (or
even a separate compiled executable) per request. extern "C" here only
fixes the symbol's name mangling for a stable dlsym() target - the
parameter types are still real C++ classes, safe to pass across the
dlopen() boundary since the host and the .so share the same
CoreUtils/JetCore headers and toolchain (a same-ABI plugin boundary, not
a foreign C one). coreutils::Exception propagates out of the call
uncaught, so the host decides how to handle a render failure rather than
the module unilaterally deciding (the executable mode's try/catch prints
to stdout and exits, which is CGI-appropriate but wrong for a persistent
server). The exported symbol name is derived from the input filename
(jetc.cpp's baseNameNoExt+sanitizeIdent) so multiple compiled templates'
.so's stay collision-free even under dlopen(..., RTLD_GLOBAL).
jetc/build-shared.sh builds the .so: JetCore's 22 tag classes are still
force-linked directly (Tag::interpretSpan's fallback path can dispatch
to any of them at runtime, regardless of what a given template
statically references), but CoreUtils objects are compiled -fPIC and
archived into a throwaway libCoreUtils_pic.a instead of being linked
directly - libCoreUtils.a itself isn't position-independent, so linking
it into a .so fails outright ("relocation ... can not be used when
making a shared object"), and force-linking every CoreUtils .cpp
directly (rather than archiving, so the linker only pulls in what's
actually referenced) surfaced an unrelated, pre-existing incomplete
class - IMFMessage's vtable is never emitted anywhere, presumably a
partially-migrated class (see project memory on IMFMultipart/IMFText
still being fd-based) - as an unresolved dlopen()-time symbol, even
though nothing here uses it. A normal archive-based link (like the
existing libCoreUtils.a already does for jet-2.0) never pulls IMFMessage
in at all, so building libCoreUtils_pic.a the same way sidesteps it
without touching the unrelated bug itself.
jetc/host_test.cpp is a minimal stand-in for the real server: dlopen()s
a compiled .so, dlsym()s its entry point, and calls it twice with fresh
jet::Global objects (simulating two separate requests against one loaded
module) to prove the .so is genuinely reusable, not single-use. Verified
both demo templates (demo.jet, demo2.jet - the latter covering nested
if/for, a stateful while loop, and the interpretSpan fallback path) build
as .so's and produce the same byte-identical output as the executable
and interpreter paths, across two calls each. Also re-verified the
existing (non-shared) executable build path still matches jet-2.0's
output after the Codegen::generate() signature change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d52ca61250
commit
a94fc5e8fb
5 files changed
+214
-9
No files matched your search
Executable → Regular
+48
-7
@@ -3,15 +3,47 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
std::string baseNameNoExt(const std::string &path) {
|
||||
size_t slash = path.find_last_of("/\\");
|
||||
std::string base = (slash == std::string::npos) ? path : path.substr(slash + 1);
|
||||
size_t dot = base.find_last_of('.');
|
||||
if (dot != std::string::npos) base = base.substr(0, dot);
|
||||
return base;
|
||||
}
|
||||
|
||||
std::string sanitizeIdent(const std::string &s) {
|
||||
std::string r;
|
||||
for (char c : s) r += (std::isalnum((unsigned char)c) || c == '_') ? c : '_';
|
||||
return r;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "usage: jetc <input.jet> <output.cpp>" << std::endl;
|
||||
bool shared = false;
|
||||
std::vector<std::string> pos;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string a = argv[i];
|
||||
if (a == "--shared") shared = true;
|
||||
else pos.push_back(a);
|
||||
}
|
||||
if (pos.size() < 2) {
|
||||
std::cerr << "usage: jetc [--shared] <input.jet> <output.cpp>" << std::endl;
|
||||
std::cerr << " --shared emit a dlsym()-able entry point (see build-shared.sh)" << std::endl;
|
||||
std::cerr << " instead of a standalone main()." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::ifstream in(argv[1]);
|
||||
const std::string &inputPath = pos[0];
|
||||
const std::string &outputPath = pos[1];
|
||||
|
||||
std::ifstream in(inputPath);
|
||||
if (!in) {
|
||||
std::cerr << "jetc: cannot open " << argv[1] << std::endl;
|
||||
std::cerr << "jetc: cannot open " << inputPath << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::ostringstream ss;
|
||||
@@ -30,11 +62,20 @@ int main(int argc, char **argv) {
|
||||
jetc::Parser parser(src);
|
||||
auto nodes = parser.parseNodes();
|
||||
jetc::Codegen codegen;
|
||||
std::string cpp = codegen.generate(nodes, std::string("compiled from ") + argv[1]);
|
||||
std::ofstream outFile(argv[2]);
|
||||
// Symbol is derived from the input filename (not the output path) so
|
||||
// it stays stable and predictable for whatever builds and dlsym()'s
|
||||
// the resulting .so - unique per template, so multiple compiled
|
||||
// templates' .so's can coexist in one process even under
|
||||
// dlopen(..., RTLD_GLOBAL) instead of requiring isolated handles.
|
||||
std::string symbol = "jetc_render_" + sanitizeIdent(baseNameNoExt(inputPath));
|
||||
std::string cpp = codegen.generate(nodes, std::string("compiled from ") + inputPath, shared, symbol);
|
||||
std::ofstream outFile(outputPath);
|
||||
outFile << cpp;
|
||||
outFile.close();
|
||||
std::cout << "jetc: wrote " << argv[2] << std::endl;
|
||||
if (shared)
|
||||
std::cout << "jetc: wrote " << outputPath << " (entry symbol: " << symbol << ")" << std::endl;
|
||||
else
|
||||
std::cout << "jetc: wrote " << outputPath << std::endl;
|
||||
} catch (std::exception &e) {
|
||||
std::cerr << "jetc: " << e.what() << std::endl;
|
||||
return 1;
|
||||
|
||||
Reference in new issue
Block a user