ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
ir_digest.cpp
Go to the documentation of this file.
1#include "ir_digest.h"
2
3#include <algorithm>
4#include <charconv>
5#include <cmath>
6#include <cstdint>
7#include <cstdlib>
8#include <cstring>
9#include <stdexcept>
10#include <string>
11#include <utility>
12#include <vector>
13
14namespace ember_ir {
15namespace {
16
17// ---------------------------------------------------------------- SHA-256 (FIPS 180-4)
18
19const uint32_t kK[64] = {
20 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
21 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
22 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
23 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
24 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
25 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
26 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
27 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
28
29inline uint32_t rotr(uint32_t x, int n) { return (x >> n) | (x << (32 - n)); }
30
31void sha256_block(uint32_t h[8], const unsigned char* p) {
32 uint32_t w[64];
33 for (int t = 0; t < 16; ++t)
34 w[t] = (uint32_t(p[4 * t]) << 24) | (uint32_t(p[4 * t + 1]) << 16) | (uint32_t(p[4 * t + 2]) << 8) | uint32_t(p[4 * t + 3]);
35 for (int t = 16; t < 64; ++t) {
36 const uint32_t s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ (w[t - 15] >> 3);
37 const uint32_t s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ (w[t - 2] >> 10);
38 w[t] = w[t - 16] + s0 + w[t - 7] + s1;
39 }
40 uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], k = h[7];
41 for (int t = 0; t < 64; ++t) {
42 const uint32_t S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
43 const uint32_t ch = (e & f) ^ (~e & g);
44 const uint32_t t1 = k + S1 + ch + kK[t] + w[t];
45 const uint32_t S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
46 const uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
47 const uint32_t t2 = S0 + maj;
48 k = g;
49 g = f;
50 f = e;
51 e = d + t1;
52 d = c;
53 c = b;
54 b = a;
55 a = t1 + t2;
56 }
57 h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e; h[5] += f; h[6] += g; h[7] += k;
58}
59
60// ---------------------------------------------------------------- I-JSON values
61
62struct Value {
63 enum Type { Null, False, True, Number, String, Array, Object } type = Null;
64 double number = 0.0;
65 bool integer = false; // an integer token (no fraction, no exponent)
66 std::string str; // String: UTF-8
67 std::vector<Value> items;
68 std::vector<std::pair<std::string, Value>> members;
69};
70
71struct NoDigest {
72 std::string why;
73};
74
75void append_utf8(std::string& out, uint32_t cp) {
76 if (cp < 0x80) {
77 out += static_cast<char>(cp);
78 } else if (cp < 0x800) {
79 out += static_cast<char>(0xC0 | (cp >> 6));
80 out += static_cast<char>(0x80 | (cp & 0x3F));
81 } else if (cp < 0x10000) {
82 out += static_cast<char>(0xE0 | (cp >> 12));
83 out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
84 out += static_cast<char>(0x80 | (cp & 0x3F));
85 } else {
86 out += static_cast<char>(0xF0 | (cp >> 18));
87 out += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
88 out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
89 out += static_cast<char>(0x80 | (cp & 0x3F));
90 }
91}
92
93// RFC 8259 grammar, strictly; the I-JSON conditions as the text is read.
94class Parser {
95 public:
96 explicit Parser(const std::string& s) : s_(s) {}
97
98 Value document() {
99 ws();
100 Value v = value(0);
101 ws();
102 if (i_ != s_.size()) fail("not JSON: text after the value at byte " + std::to_string(i_));
103 return v;
104 }
105
106 private:
107 [[noreturn]] void fail(const std::string& why) const { throw NoDigest{why}; }
108
109 void ws() {
110 while (i_ < s_.size() && (s_[i_] == ' ' || s_[i_] == '\t' || s_[i_] == '\n' || s_[i_] == '\r')) ++i_;
111 }
112
113 bool lit(const char* w) {
114 const size_t n = std::strlen(w);
115 if (s_.compare(i_, n, w) != 0) return false;
116 i_ += n;
117 return true;
118 }
119
120 Value value(int depth) {
121 if (depth > 10000) fail("not JSON: nested too deeply");
122 if (i_ >= s_.size()) fail("not JSON: a value expected at the end");
123 const char c = s_[i_];
124 Value v;
125 if (c == '{') {
126 v.type = Value::Object;
127 ++i_;
128 ws();
129 if (i_ < s_.size() && s_[i_] == '}') {
130 ++i_;
131 return v;
132 }
133 for (;;) {
134 ws();
135 if (i_ >= s_.size() || s_[i_] != '"') fail("not JSON: a member name expected at byte " + std::to_string(i_));
136 std::string name = string();
137 for (const auto& m : v.members)
138 if (m.first == name) fail("a duplicate member name");
139 ws();
140 if (i_ >= s_.size() || s_[i_] != ':') fail("not JSON: ':' expected at byte " + std::to_string(i_));
141 ++i_;
142 ws();
143 Value item = value(depth + 1);
144 v.members.emplace_back(std::move(name), std::move(item));
145 ws();
146 if (i_ < s_.size() && s_[i_] == ',') {
147 ++i_;
148 continue;
149 }
150 if (i_ < s_.size() && s_[i_] == '}') {
151 ++i_;
152 return v;
153 }
154 fail("not JSON: ',' or '}' expected at byte " + std::to_string(i_));
155 }
156 }
157 if (c == '[') {
158 v.type = Value::Array;
159 ++i_;
160 ws();
161 if (i_ < s_.size() && s_[i_] == ']') {
162 ++i_;
163 return v;
164 }
165 for (;;) {
166 ws();
167 v.items.push_back(value(depth + 1));
168 ws();
169 if (i_ < s_.size() && s_[i_] == ',') {
170 ++i_;
171 continue;
172 }
173 if (i_ < s_.size() && s_[i_] == ']') {
174 ++i_;
175 return v;
176 }
177 fail("not JSON: ',' or ']' expected at byte " + std::to_string(i_));
178 }
179 }
180 if (c == '"') {
181 v.type = Value::String;
182 v.str = string();
183 return v;
184 }
185 if (lit("true")) {
186 v.type = Value::True;
187 return v;
188 }
189 if (lit("false")) {
190 v.type = Value::False;
191 return v;
192 }
193 if (lit("null")) return v;
194 if (c == '-' || (c >= '0' && c <= '9')) return number();
195 fail("not JSON: unexpected character at byte " + std::to_string(i_));
196 }
197
198 Value number() {
199 const size_t b = i_;
200 bool neg = false;
201 if (s_[i_] == '-') {
202 neg = true;
203 ++i_;
204 }
205 auto digits = [&] {
206 const size_t d0 = i_;
207 while (i_ < s_.size() && s_[i_] >= '0' && s_[i_] <= '9') ++i_;
208 return i_ - d0;
209 };
210 const size_t int0 = i_;
211 if (i_ < s_.size() && s_[i_] == '0') {
212 ++i_;
213 } else if (digits() == 0) {
214 fail("not JSON: a malformed number at byte " + std::to_string(b));
215 }
216 const std::string int_digits = s_.substr(int0, i_ - int0);
217 bool integer = true;
218 if (i_ < s_.size() && s_[i_] == '.') {
219 ++i_;
220 integer = false;
221 if (digits() == 0) fail("not JSON: a malformed number at byte " + std::to_string(b));
222 }
223 if (i_ < s_.size() && (s_[i_] == 'e' || s_[i_] == 'E')) {
224 ++i_;
225 integer = false;
226 if (i_ < s_.size() && (s_[i_] == '+' || s_[i_] == '-')) ++i_;
227 if (digits() == 0) fail("not JSON: a malformed number at byte " + std::to_string(b));
228 }
229 const std::string tok = s_.substr(b, i_ - b);
230 Value v;
231 v.type = Value::Number;
232 v.integer = integer;
233 if (integer) {
234 // I-JSON: an integer within -2^53 .. 2^53 (compared as digits: a double would round the limit).
235 static const std::string kLimit = "9007199254740992";
236 if (int_digits.size() > kLimit.size() || (int_digits.size() == kLimit.size() && int_digits > kLimit))
237 fail("the integer " + tok + " is outside I-JSON's exact range (2^53)");
238 v.number = std::strtod(int_digits.c_str(), nullptr); // exact: at most 2^53
239 if (neg && v.number != 0.0) v.number = -v.number; // "-0" is the integer zero
240 return v;
241 }
242 v.number = std::strtod(tok.c_str(), nullptr); // correctly rounded (C locale)
243 if (!std::isfinite(v.number)) fail("the number " + tok + " is not finite in binary64");
244 return v;
245 }
246
247 uint32_t hex4() {
248 if (i_ + 4 > s_.size()) fail("not JSON: a short \\u escape");
249 uint32_t v = 0;
250 for (int k = 0; k < 4; ++k) {
251 const char c = s_[i_++];
252 v <<= 4;
253 if (c >= '0' && c <= '9')
254 v |= c - '0';
255 else if (c >= 'a' && c <= 'f')
256 v |= c - 'a' + 10;
257 else if (c >= 'A' && c <= 'F')
258 v |= c - 'A' + 10;
259 else
260 fail("not JSON: a malformed \\u escape");
261 }
262 return v;
263 }
264
265 // A JSON string at i_ (the opening quote), as UTF-8; refuses lone surrogates and invalid UTF-8.
266 std::string string() {
267 ++i_;
268 std::string out;
269 for (;;) {
270 if (i_ >= s_.size()) fail("not JSON: an unterminated string");
271 const unsigned char c = static_cast<unsigned char>(s_[i_]);
272 if (c == '"') {
273 ++i_;
274 return out;
275 }
276 if (c < 0x20) fail("not JSON: a control character in a string");
277 if (c == '\\') {
278 ++i_;
279 if (i_ >= s_.size()) fail("not JSON: an unterminated string");
280 const char e = s_[i_++];
281 switch (e) {
282 case '"': out += '"'; break;
283 case '\\': out += '\\'; break;
284 case '/': out += '/'; break;
285 case 'b': out += '\b'; break;
286 case 'f': out += '\f'; break;
287 case 'n': out += '\n'; break;
288 case 'r': out += '\r'; break;
289 case 't': out += '\t'; break;
290 case 'u': {
291 uint32_t cp = hex4();
292 if (cp >= 0xDC00 && cp <= 0xDFFF) fail("a string holds a lone surrogate");
293 if (cp >= 0xD800 && cp <= 0xDBFF) {
294 if (i_ + 6 > s_.size() || s_[i_] != '\\' || s_[i_ + 1] != 'u') fail("a string holds a lone surrogate");
295 i_ += 2;
296 const uint32_t lo = hex4();
297 if (lo < 0xDC00 || lo > 0xDFFF) fail("a string holds a lone surrogate");
298 cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
299 }
301 break;
302 }
303 default: fail("not JSON: an unknown escape in a string");
304 }
305 continue;
306 }
307 // Raw UTF-8: well-formed sequences only (no overlong forms, no surrogates, at most U+10FFFF).
308 int n = 0;
309 uint32_t cp = 0;
310 if (c < 0x80) {
311 n = 1;
312 cp = c;
313 } else if ((c & 0xE0) == 0xC0) {
314 n = 2;
315 cp = c & 0x1F;
316 } else if ((c & 0xF0) == 0xE0) {
317 n = 3;
318 cp = c & 0x0F;
319 } else if ((c & 0xF8) == 0xF0) {
320 n = 4;
321 cp = c & 0x07;
322 } else {
323 fail("a string is not well-formed UTF-8");
324 }
325 if (i_ + n > s_.size()) fail("a string is not well-formed UTF-8");
326 for (int k = 1; k < n; ++k) {
327 const unsigned char cc = static_cast<unsigned char>(s_[i_ + k]);
328 if ((cc & 0xC0) != 0x80) fail("a string is not well-formed UTF-8");
329 cp = (cp << 6) | (cc & 0x3F);
330 }
331 static const uint32_t kMin[5] = {0, 0, 0x80, 0x800, 0x10000};
332 if (cp < kMin[n] || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) fail("a string is not well-formed UTF-8");
333 out.append(s_, i_, n);
334 i_ += n;
335 }
336 }
337
338 const std::string& s_;
339 size_t i_ = 0;
340};
341
342bool holds_negative_zero(const Value& v) {
343 switch (v.type) {
344 case Value::Number: return !v.integer && v.number == 0.0 && std::signbit(v.number);
345 case Value::Array:
346 for (const auto& x : v.items)
347 if (holds_negative_zero(x)) return true;
348 return false;
349 case Value::Object:
350 for (const auto& m : v.members)
351 if (holds_negative_zero(m.second)) return true;
352 return false;
353 default: return false;
354 }
355}
356
357// UTF-8 to UTF-16 code units (the text is well-formed: the parser checked it).
358std::vector<uint16_t> utf16(const std::string& s) {
359 std::vector<uint16_t> out;
360 for (size_t i = 0; i < s.size();) {
361 const unsigned char c = static_cast<unsigned char>(s[i]);
362 uint32_t cp;
363 int n;
364 if (c < 0x80) {
365 cp = c;
366 n = 1;
367 } else if ((c & 0xE0) == 0xC0) {
368 cp = c & 0x1F;
369 n = 2;
370 } else if ((c & 0xF0) == 0xE0) {
371 cp = c & 0x0F;
372 n = 3;
373 } else {
374 cp = c & 0x07;
375 n = 4;
376 }
378 i += n;
379 if (cp >= 0x10000) {
380 cp -= 0x10000;
381 out.push_back(static_cast<uint16_t>(0xD800 + (cp >> 10)));
382 out.push_back(static_cast<uint16_t>(0xDC00 + (cp & 0x3FF)));
383 } else {
384 out.push_back(static_cast<uint16_t>(cp));
385 }
386 }
387 return out;
388}
389
390// JSON.stringify of a string: '"' and '\' escaped, the controls U+0000 .. U+001F as \b \t \n \f \r or
391// \u00xx (lowercase hex); everything else as it is (UTF-8).
392void write_string(std::string& out, const std::string& s) {
393 static const char* hex = "0123456789abcdef";
394 out += '"';
395 for (const char ch : s) {
396 const unsigned char c = static_cast<unsigned char>(ch);
397 switch (c) {
398 case '"': out += "\\\""; break;
399 case '\\': out += "\\\\"; break;
400 case '\b': out += "\\b"; break;
401 case '\t': out += "\\t"; break;
402 case '\n': out += "\\n"; break;
403 case '\f': out += "\\f"; break;
404 case '\r': out += "\\r"; break;
405 default:
406 if (c < 0x20) {
407 out += "\\u00";
408 out += hex[c >> 4];
409 out += hex[c & 15];
410 } else {
411 out += ch;
412 }
413 }
414 }
415 out += '"';
416}
417
418void write(std::string& out, const Value& v, bool prose) {
419 switch (v.type) {
420 case Value::Null: out += "null"; return;
421 case Value::False: out += "false"; return;
422 case Value::True: out += "true"; return;
423 case Value::Number: out += es_number_string(v.number); return;
424 case Value::String: write_string(out, v.str); return;
425 case Value::Array: {
426 out += '[';
427 for (size_t k = 0; k < v.items.size(); ++k) {
428 if (k) out += ',';
429 write(out, v.items[k], prose);
430 }
431 out += ']';
432 return;
433 }
434 case Value::Object: {
435 std::vector<std::pair<std::vector<uint16_t>, const std::pair<std::string, Value>*>> keep;
436 for (const auto& m : v.members)
437 if (prose || m.first.empty() || m.first[0] != '_') keep.emplace_back(utf16(m.first), &m);
438 std::sort(keep.begin(), keep.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
439 out += '{';
440 for (size_t k = 0; k < keep.size(); ++k) {
441 if (k) out += ',';
442 write_string(out, keep[k].second->first);
443 out += ':';
444 write(out, keep[k].second->second, prose);
445 }
446 out += '}';
447 return;
448 }
449 }
450}
451
452} // namespace
453
454std::string sha256_hex_bytes(const std::string& bytes) {
455 uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
456 const uint64_t bits = static_cast<uint64_t>(bytes.size()) * 8;
457 std::string msg = bytes;
458 msg += static_cast<char>(0x80);
459 while (msg.size() % 64 != 56) msg += '\0';
460 for (int k = 7; k >= 0; --k) msg += static_cast<char>((bits >> (8 * k)) & 0xFF);
461 for (size_t off = 0; off < msg.size(); off += 64) sha256_block(h, reinterpret_cast<const unsigned char*>(msg.data()) + off);
462 static const char* hex = "0123456789abcdef";
463 std::string out;
464 for (uint32_t x : h)
465 for (int k = 7; k >= 0; --k) out += hex[(x >> (4 * k)) & 15];
466 return out;
467}
468
469// ECMAScript Number::toString(x) (ECMA-262 Number::toString, radix 10) for a finite x: the shortest digits
470// s (k of them) with s * 10^(n - k) round-tripping to x, closest to x on a tie (std::to_chars's shortest
471// scientific form), then laid out by n.
472std::string es_number_string(double v) {
473 if (v == 0.0) return "0";
474 if (v < 0) return "-" + es_number_string(-v);
475 char buf[64];
476 const auto r = std::to_chars(buf, buf + sizeof(buf), v, std::chars_format::scientific);
477 if (r.ec != std::errc()) throw std::runtime_error("es_number_string: to_chars failed");
478 const std::string sci(buf, r.ptr);
479 const size_t e = sci.find('e');
480 std::string digits;
481 for (size_t i = 0; i < e; ++i)
482 if (sci[i] != '.') digits += sci[i];
483 const int exp10 = std::atoi(sci.c_str() + e + 1);
484 const int k = static_cast<int>(digits.size()), n = exp10 + 1;
485 if (k <= n && n <= 21) return digits + std::string(static_cast<size_t>(n - k), '0');
486 if (0 < n && n <= 21) return digits.substr(0, n) + "." + digits.substr(n);
487 if (-6 < n && n <= 0) return "0." + std::string(static_cast<size_t>(-n), '0') + digits;
488 const int ex = n - 1;
489 std::string out = digits.substr(0, 1);
490 if (k > 1) out += "." + digits.substr(1);
491 out += ex >= 0 ? "e+" : "e-";
492 out += std::to_string(ex >= 0 ? ex : -ex);
493 return out;
494}
495
496DigestResult ir_digest(const std::string& text, bool prose) {
498 try {
499 const Value v = Parser(text).document();
500 if (holds_negative_zero(v)) throw NoDigest{"a negative zero (RFC 8785 writes it as 0, so it would share +0's digest)"};
501 write(r.canonical, v, prose);
502 } catch (const NoDigest& e) {
503 r.refused = e.why;
504 r.canonical.clear();
505 return r;
506 }
507 r.sha256 = sha256_hex_bytes(r.canonical);
508 r.ok = true;
509 return r;
510}
511
512} // namespace ember_ir
std::string why
Definition graph_ir.cpp:280
const float * p[kIrDepthSourcesMax]
int n
float value
std::string text
const std::string & s_
std::vector< float > v
const char * name
Tok tok
float c
size_t i_
bool integer
std::vector< E > a
node out
size_t j
double number
Definition ir_digest.cpp:64
std::vector< Value > items
Definition ir_digest.cpp:67
std::string str
Definition ir_digest.cpp:66
enum ember_ir::@44::Value::Type type
std::vector< std::pair< std::string, Value > > members
Definition ir_digest.cpp:68
Definition api.h:20
DigestResult ir_digest(const std::string &text, bool prose)
std::string sha256_hex_bytes(const std::string &bytes)
std::string es_number_string(double v)
void append_utf8(uint32_t cpt, std::string &out)