include/pops/runtime/program/external_brick.hpp Source FileΒΆ

adc_cpp: include/pops/runtime/program/external_brick.hpp Source File
adc_cpp 0.3.0
Model-free C++23 core for coupled hyperbolic-elliptic systems on adaptive (AMR) meshes, with MPI and GPU (Kokkos) backends
external_brick.hpp
Go to the documentation of this file.
1#pragma once
2
3// Process-global registry of EXTERNAL C++ bricks (Spec 3 section 21-22, criterion 20, ADC-463). A
4// Spec 3 brick is native / generated / macro / external-C++; this header owns the last category. A
5// user ships a brick in a standalone `.so` that, at static-init time, registers a manifest entry via
6// `POPS_REGISTER_BRICK(id, category, requirements)`. The host then exports a C `pops_brick_manifest()`
7// function (JSON over `BrickRegistry::instance().ids()` + each entry) that `pops.lib.load_cpp_library`
8// dlopens and parses, so `pops.lib.riemann.User("id")` surfaces a real, requirement-carrying
9// descriptor. This is a HOST registry (no POPS_HD, no device state): it catalogs the brick's identity
10// and requirements; the brick's numerical kernel stays a separate concern wired by the codegen.
11
12#include <cstddef>
13#include <string>
14#include <unordered_map>
15#include <vector>
16
17namespace pops::runtime::program {
18
19// Escapes a string so a manifest field built from a user-supplied id/category/CSV is always valid
20// JSON: the two structural characters (`"` and `\`) plus every control character (`\n`, `\r`, `\t`,
21// and the rest of `\x00`-`\x1f` as `\uXXXX`), which RFC 8259 forbids unescaped inside a string. A
22// raw control character would make `json.loads` (lib.py) reject the whole manifest, so the escape
23// must be complete, not just the two structural chars. The C++ reader (`field`) reverses it.
24inline std::string json_escape(const std::string& s) {
25 static const char kHex[] = "0123456789abcdef";
26 std::string out;
27 out.reserve(s.size());
28 for (char c : s) {
29 switch (c) {
30 case '"':
31 out += "\\\"";
32 break;
33 case '\\':
34 out += "\\\\";
35 break;
36 case '\n':
37 out += "\\n";
38 break;
39 case '\r':
40 out += "\\r";
41 break;
42 case '\t':
43 out += "\\t";
44 break;
45 case '\b':
46 out += "\\b";
47 break;
48 case '\f':
49 out += "\\f";
50 break;
51 default:
52 if (static_cast<unsigned char>(c) < 0x20) {
53 out += "\\u00";
54 out.push_back(kHex[(static_cast<unsigned char>(c) >> 4) & 0xf]);
55 out.push_back(kHex[static_cast<unsigned char>(c) & 0xf]);
56 } else {
57 out.push_back(c);
58 }
59 }
60 }
61 return out;
62}
63
64// Reverses json_escape so a manifest reader recovers the raw value json_escape encoded: \" \\ \/
65// \n \r \t \b \f and \uXXXX (manifest tokens are ASCII, so a \u escape only ever carries a control
66// byte) back to the character. The C++ field reader uses this to match what json.loads (lib.py)
67// yields from the same manifest. A trailing lone backslash or a truncated \u is copied verbatim.
68inline std::string json_unescape(const std::string& s) {
69 std::string out;
70 out.reserve(s.size());
71 for (std::size_t i = 0; i < s.size(); ++i) {
72 if (s[i] != '\\' || i + 1 >= s.size()) {
73 out.push_back(s[i]);
74 continue;
75 }
76 const char n = s[++i];
77 switch (n) {
78 case 'n':
79 out.push_back('\n');
80 break;
81 case 'r':
82 out.push_back('\r');
83 break;
84 case 't':
85 out.push_back('\t');
86 break;
87 case 'b':
88 out.push_back('\b');
89 break;
90 case 'f':
91 out.push_back('\f');
92 break;
93 case 'u':
94 if (i + 4 < s.size()) {
95 out.push_back(static_cast<char>(std::stoi(s.substr(i + 1, 4), nullptr, 16) & 0xff));
96 i += 4;
97 }
98 break;
99 default:
100 out.push_back(n);
101 break; // \" \\ \/ and any other escaped char -> itself
102 }
103 }
104 return out;
105}
106
107// The manifest of one external C++ brick: its identity plus the requirements/capabilities the
108// selector and codegen need. All fields are host strings (no device data); `requirements` and
109// `capabilities` are comma-separated lists (the manifest's wire form), empty when none.
111 std::string id; // the brick id a user selects (e.g. "my_hllc")
112 std::string category; // the catalog slot ("riemann", "preconditioner", ...)
113 std::string requirements; // CSV of required model capabilities ("pressure,wave_speeds"), or ""
114 std::string capabilities; // CSV of capabilities the brick PROVIDES, or ""
115};
116
117// A process-global catalog of registered external bricks, keyed by id. Populated at static-init
118// time by `POPS_REGISTER_BRICK` (in the user's `.so`) and read by the host's `pops_brick_manifest()`
119// exporter. Construction order across translation units is unspecified, so the registry is a
120// function-local static (the Meyers singleton) -- it is constructed on first use, before any
121// `POPS_REGISTER_BRICK` static initializer can run against it.
123 public:
124 // The single process-global instance.
126 static BrickRegistry registry;
127 return registry;
128 }
129
130 // Register (or replace) `entry` under `entry.id`. Idempotent on id: re-registering the same id
131 // overwrites the manifest and never duplicates the id in `ids()` (last registration wins).
133 auto it = index_.find(entry.id);
134 if (it == index_.end()) {
135 index_.emplace(entry.id, entries_.size());
136 entries_.push_back(entry);
137 ids_.push_back(entry.id);
138 } else {
139 entries_[it->second] = entry;
140 }
141 }
142
143 // The manifest of brick `id`, or nullptr if no such brick is registered (never throws).
144 const BrickManifestEntry* lookup(const std::string& id) const {
145 auto it = index_.find(id);
146 return it == index_.end() ? nullptr : &entries_[it->second];
147 }
148
149 // Every registered brick id, in registration order.
150 const std::vector<std::string>& ids() const { return ids_; }
151
152 // Every registered manifest entry, in registration order.
153 const std::vector<BrickManifestEntry>& entries() const { return entries_; }
154
155 // The manifest of every registered brick as the JSON `pops.lib.load_cpp_library` parses:
156 // {"bricks": [{"id", "category", "requirements", "capabilities"}, ...]}
157 // requirements/capabilities are the CSV strings the macro registered (empty when none). This is
158 // the wire form a brick `.so` exports through `pops_brick_manifest()` (POPS_DEFINE_BRICK_MANIFEST);
159 // the host dlopens the `.so` and feeds the returned string to `_register_manifest`.
160 std::string to_json() const {
161 std::string out = "{\"bricks\":[";
162 for (std::size_t k = 0; k < entries_.size(); ++k) {
163 const BrickManifestEntry& e = entries_[k];
164 if (k != 0)
165 out += ',';
166 out += "{\"id\":\"" + json_escape(e.id) + "\",\"category\":\"" + json_escape(e.category) +
167 "\",\"requirements\":\"" + json_escape(e.requirements) + "\",\"capabilities\":\"" +
168 json_escape(e.capabilities) + "\"}";
169 }
170 out += "]}";
171 return out;
172 }
173
174 std::size_t size() const { return entries_.size(); }
175
176 void clear() {
177 entries_.clear();
178 ids_.clear();
179 index_.clear();
180 }
181
182 private:
183 BrickRegistry() = default;
184
185 std::vector<BrickManifestEntry> entries_; // registration-order manifest list
186 std::vector<std::string> ids_; // registration-order id list (mirrors entries_)
187 std::unordered_map<std::string, std::size_t> index_; // id -> index into entries_
188};
189
190// Registers a manifest entry at static-init time. Use at namespace scope in a brick's `.so`:
191// POPS_REGISTER_BRICK("my_hllc", "riemann", "pressure,wave_speeds");
192// The capabilities field is left empty by this 3-argument form; a brick that PROVIDES capabilities
193// calls `BrickRegistry::instance().register_brick({...})` directly. The trailing static is a unique
194// dummy whose initializer performs the registration (zero-cost at runtime, runs once before main).
195#define POPS_REGISTER_BRICK(brick_id, brick_category, brick_requirements) \
196 static const bool POPS_REGISTER_BRICK_CAT_(pops_brick_registered_, __LINE__) = [] { \
197 ::pops::runtime::program::BrickRegistry::instance().register_brick( \
198 {(brick_id), (brick_category), (brick_requirements), ""}); \
199 return true; \
200 }()
201
202// Token-paste helpers so several POPS_REGISTER_BRICK uses in one TU get distinct static names.
203#define POPS_REGISTER_BRICK_CAT_(a, b) POPS_REGISTER_BRICK_CAT2_(a, b)
204#define POPS_REGISTER_BRICK_CAT2_(a, b) a##b
205
206// Exports the C reader `pops.lib.load_cpp_library` dlopens after opening a brick `.so`. Use ONCE at
207// namespace scope in the brick's `.so` (after its POPS_REGISTER_BRICK calls have populated the
208// registry at static-init time):
209// POPS_REGISTER_BRICK("my_hllc", "riemann", "pressure,wave_speeds");
210// POPS_DEFINE_BRICK_MANIFEST();
211// It returns the JSON of EVERY brick the `.so` registered (BrickRegistry::to_json). The string is a
212// function-local static built once on first call (the registry is fully populated by then, since the
213// static initializers ran before any host call), so the `const char*` stays valid for the process.
214#define POPS_DEFINE_BRICK_MANIFEST() \
215 extern "C" const char* pops_brick_manifest() { \
216 static const std::string manifest = \
217 ::pops::runtime::program::BrickRegistry::instance().to_json(); \
218 return manifest.c_str(); \
219 }
220
221} // namespace pops::runtime::program
Definition external_brick.hpp:122
std::string to_json() const
Definition external_brick.hpp:160
const std::vector< std::string > & ids() const
Definition external_brick.hpp:150
const std::vector< BrickManifestEntry > & entries() const
Definition external_brick.hpp:153
void clear()
Definition external_brick.hpp:176
void register_brick(const BrickManifestEntry &entry)
Definition external_brick.hpp:132
const BrickManifestEntry * lookup(const std::string &id) const
Definition external_brick.hpp:144
std::size_t size() const
Definition external_brick.hpp:174
static BrickRegistry & instance()
Definition external_brick.hpp:125
Definition cache_manager.hpp:37
std::string json_unescape(const std::string &s)
Definition external_brick.hpp:68
std::string json_escape(const std::string &s)
Definition external_brick.hpp:24
Definition external_brick.hpp:110
std::string id
Definition external_brick.hpp:111
std::string capabilities
Definition external_brick.hpp:114
std::string category
Definition external_brick.hpp:112
std::string requirements
Definition external_brick.hpp:113