aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/client.cpp1553
-rw-r--r--core/common.c3
-rw-r--r--core/compress.c79
-rw-r--r--core/logger.c105
-rw-r--r--core/salis.c1139
-rw-r--r--core/server.c418
-rw-r--r--core/sql.c73
7 files changed, 3370 insertions, 0 deletions
diff --git a/core/client.cpp b/core/client.cpp
new file mode 100644
index 0000000..771f626
--- /dev/null
+++ b/core/client.cpp
@@ -0,0 +1,1553 @@
+// index
+// [section] includes
+// [section] macros & enums
+// [section] string comparator declaration
+// [section] trace declarations
+// [section] plot declarations
+// [section] plots
+// [section] global variables
+// [section] string comparator definition
+// [section] Trace (base) definition
+// [section] TraceNamed definition
+// [section] TraceHeatmap definition
+// [section] Plot (base) definition
+// [section] PlotLines definition
+// [section] PlotStacked definition
+// [section] PlotHeatmap definition
+// [section] data functions
+// [section] gui functions
+// [section] main functions
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <arpa/inet.h>
+#include <GLFW/glfw3.h>
+#include <imgui_impl_glfw.h>
+#include <imgui_impl_opengl3.h>
+#include <implot.h>
+#include <implot_internal.h>
+#include <json-c/json.h>
+#include <signal.h>
+#include <threads.h>
+
+#include <algorithm>
+#include <array>
+#include <map>
+#include <vector>
+
+#include "common.c"
+#include "logger.c"
+
+// ----------------------------------------------------------------------------
+// [section] macros & enums
+// ----------------------------------------------------------------------------
+#define COLOR_BLACK ImVec4(0.f, 0.f, 0.f, 1.f)
+#define FONT_SIZE 12.f
+#define GLSL_VERSION "#version 130"
+#define PLOT_MIN_COLS 1
+#define PLOT_MAX_COLS 8
+#define PLOT_MIN_HEIGHT 100.f
+#define PLOT_MAX_HEIGHT 800.f
+#define PLOT_HEIGHT_INTERVAL 50.f
+#define PLOT_SCROLL_MARGIN 28.f
+#define HM_SCALE_POW_MIN -1.f
+#define HM_SCALE_POW_MAX 64.f
+#define HM_SCALE_POW_INTERVAL 1.f
+#define HM_COLORSCALE_WIDTH 80.f
+
+#define IMGUI_WINDOW_FLAGS ( \
+ ImGuiWindowFlags_NoBackground | \
+ ImGuiWindowFlags_NoDecoration | \
+ ImGuiWindowFlags_NoMove | \
+ ImGuiWindowFlags_NoSavedSettings \
+)
+
+#define DATA_FETCH_INTERVAL 10
+#define DATA_FETCH_INTERVAL_SUBDIV 1000
+
+#define DEFVAL_ENTRIES 0x800l
+#define DEFVAL_NTH 1l
+#define DEFVAL_X_AXIS 0
+#define DEFVAL_X_LOW 0l
+#define DEFVAL_X_HIGH INT64_MAX
+#define DEFVAL_HM_LEFT 0l
+
+enum Status {
+ STATUS_STOPPED,
+ STATUS_RUNNING,
+ STATUS_FETCHING,
+ STATUS_STOPPING,
+};
+
+// ----------------------------------------------------------------------------
+// [section] string comparator declaration
+// ----------------------------------------------------------------------------
+struct StringComparator {
+ bool operator()(const char *a, const char *b) const;
+};
+
+// ----------------------------------------------------------------------------
+// [section] trace declarations
+// ----------------------------------------------------------------------------
+template <class T>
+struct Trace : public std::vector<T> {
+ Trace();
+ virtual ~Trace();
+ virtual size_t start_offset() const;
+ virtual void trim();
+#if !defined(NDEBUG)
+ virtual void validate() const;
+#endif
+ void clear();
+ void push_back(T value);
+ T *start();
+ T &operator[](size_t n);
+ T operator[](size_t n) const;
+protected:
+ void trim_spec(int64_t multiplier = 1);
+};
+
+template <class T>
+struct TraceNamed : public Trace<T> {
+ TraceNamed(const char *name, const char *label);
+ const char *get_name() const;
+ const char *get_label() const;
+private:
+ const char *m_name;
+ const char *m_label;
+};
+
+template <class T>
+struct TraceHeatmap : public TraceNamed<T> {
+ TraceHeatmap(const char *name, const char *label);
+ size_t start_offset() const;
+ void trim();
+#if !defined(NDEBUG)
+ void validate() const;
+#endif
+};
+
+// ----------------------------------------------------------------------------
+// [section] plot declarations
+// ----------------------------------------------------------------------------
+typedef int (*AxisFormatter)(double value, char *buff, int size, void *data);
+
+struct Plot {
+ Plot(const char *name, const char *section);
+ virtual ~Plot();
+ virtual void handle_input(int key, int mods);
+ virtual void flag_for_reset();
+ const char *get_name() const;
+ const char *get_section() const;
+ void render(const ImVec2 &frame_size);
+ bool m_visible;
+private:
+ static int hex_formatter(double value, char *buff, int size, void *data);
+ virtual AxisFormatter x_formatter() const;
+ virtual AxisFormatter y_formatter() const;
+ virtual float right_margin() const;
+ virtual int flags() const;
+ virtual void render_internal(const ImVec2 &frame_size) = 0;
+ virtual void render_post(const ImVec2 &frame_size);
+ const char *m_name;
+ const char *m_section;
+};
+
+struct PlotLines : public Plot {
+ PlotLines(const char *name, const char *section, std::vector<const char *> trace_keys);
+private:
+ void render_internal(const ImVec2 &frame_size);
+ std::vector<const char *> m_trace_keys;
+};
+
+struct PlotStacked : public Plot {
+ PlotStacked(const char *name, const char *section, std::vector<const char *> trace_keys);
+ void flag_for_reset();
+private:
+ static int percent_formatter(double value, char *buff, int size, void *data);
+ AxisFormatter y_formatter() const;
+ void render_internal(const ImVec2 &frame_size);
+ std::vector<const char *> m_trace_keys;
+ std::vector<bool> m_trace_states;
+ Trace<ImS64> m_trace_totals;
+ std::vector<Trace<double>> m_trace_normals;
+ bool m_needs_reset;
+};
+
+struct PlotHeatmap : public Plot {
+ PlotHeatmap(const char *name, const char *section, const char *trace_key);
+ void handle_input(int key, int mods);
+ void flag_for_reset();
+private:
+ float right_margin() const;
+ int flags() const;
+ void render_internal(const ImVec2 &frame_size);
+ void render_end(const ImVec2 &frame_size);
+ void render_post(const ImVec2 &frame_size);
+ const char *m_trace_key;
+ float m_tex_scale_pow;
+ float m_tex_scale_high;
+ size_t m_tex_rendered_last;
+ size_t m_rows_rendered;
+ GLuint m_tex_id;
+ std::vector<uint8_t> m_tex_render;
+ bool m_needs_reset;
+};
+
+// ----------------------------------------------------------------------------
+// [section] plots
+// ----------------------------------------------------------------------------
+#include "plots.cpp"
+
+std::array g_core_traces = std::to_array<TraceNamed<ImS64>>({
+ {"rowid", "rowid"},
+ {"step", "step"},
+#define FOR_CORE(i) \
+ {"cycl_" #i, "cycl_" #i}, \
+ {"mall_" #i, "mall_" #i}, \
+ {"pnum_" #i, "pnum_" #i}, \
+ {"pfst_" #i, "pfst_" #i}, \
+ {"plst_" #i, "plst_" #i}, \
+ {"amb0_" #i, "amb0_" #i}, \
+ {"amb1_" #i, "amb1_" #i}, \
+ {"emb0_" #i, "emb0_" #i}, \
+ {"emb1_" #i, "emb1_" #i}, \
+ {"eliv_" #i, "eliv_" #i}, \
+ {"edea_" #i, "edea_" #i},
+ FOR_CORES
+#undef FOR_CORE
+});
+
+std::array g_core_traces_heatmaps = std::to_array<TraceHeatmap<ImS64>>({
+#define FOR_CORE(i) \
+ {"aev_" #i, "aev_" #i}, \
+ {"eev_" #i, "eev_" #i}, \
+ {"bev_" #i, "bev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+});
+
+std::array g_core_plots = std::to_array<PlotLines>({
+ {"cycl", "general", {
+#define FOR_CORE(i) "cycl_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"mall", "general", {
+#define FOR_CORE(i) "mall_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"pnum", "general", {
+#define FOR_CORE(i) "pnum_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"ppop", "general", {
+#define FOR_CORE(i) "pfst_" #i, "plst_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"ambs", "general", {
+#define FOR_CORE(i) "amb0_" #i, "amb1_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"eevs", "general", {
+#define FOR_CORE(i) "emb0_" #i, "emb1_" #i, "eliv_" #i, "edea_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+});
+
+std::array g_core_plots_heatmaps = std::to_array<PlotHeatmap>({
+#define FOR_CORE(i) \
+ {"aev_" #i, "heatmaps", "aev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+#define FOR_CORE(i) \
+ {"eev_" #i, "heatmaps", "eev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+#define FOR_CORE(i) \
+ {"bev_" #i, "heatmaps", "bev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+});
+
+// ----------------------------------------------------------------------------
+// [section] global variables
+// ----------------------------------------------------------------------------
+GLFWwindow *g_window;
+ImGuiIO *g_imgui_io;
+ImGuiStyle *g_imgui_style;
+ImPlotStyle *g_implot_style;
+
+std::array g_x_axes = std::to_array<const char *>({
+ "rowid",
+ "step",
+#define FOR_CORE(i) "cycl_" #i,
+ FOR_CORES
+#undef FOR_CORE
+});
+
+int g_status;
+int g_x_axis = DEFVAL_X_AXIS;
+int64_t g_entries = DEFVAL_ENTRIES;
+int64_t g_nth = DEFVAL_NTH;
+int64_t g_x_low = DEFVAL_X_LOW;
+int64_t g_x_high = DEFVAL_X_HIGH;
+int64_t g_hm_left = DEFVAL_HM_LEFT;
+int64_t g_hm_pixel_count = DEFVAL_HM_PIXEL_COUNT;
+int64_t g_hm_pixel_pow; // calculate on init
+int64_t g_x_current = -1l;
+int64_t g_trace_len;
+int64_t g_trace_offset;
+
+thrd_t g_fetching_thread;
+mtx_t g_fetching_mutex;
+
+bool g_data_col_visible = true;
+bool g_plot_maximized;
+bool g_plot_scroll;
+float g_plot_scroll_current;
+float g_plot_scroll_to;
+float g_data_col_width;
+std::vector<Plot *> g_plot_cells;
+std::vector<float> g_plot_cells_top;
+std::vector<float> g_plot_cells_bottom;
+std::vector<bool> g_plots_covered;
+Plot *g_plot_selected;
+Plot *g_plot_hovered;
+int g_plot_stride;
+int g_downsampled_trace_len;
+int g_plot_cols = 2;
+size_t g_plot_col_selected;
+size_t g_plot_row_selected;
+float g_plot_height = 300.f;
+ImPlotColormap g_hm_colormap_id;
+
+std::map<const char *, TraceNamed<ImS64> *, StringComparator> g_trace_map;
+std::vector<TraceNamed<ImS64> *> g_traces;
+std::vector<Plot *> g_plots;
+Trace<double> g_x_axis_double;
+Trace<double> g_zero_trace;
+
+std::array g_hm_colormap = std::to_array<ImVec4>({
+ {0.000f, 0.000f, 0.016f, 1.f},
+ {0.106f, 0.047f, 0.255f, 1.f},
+ {0.290f, 0.047f, 0.420f, 1.f},
+ {0.471f, 0.110f, 0.427f, 1.f},
+ {0.647f, 0.173f, 0.376f, 1.f},
+ {0.812f, 0.267f, 0.275f, 1.f},
+ {0.929f, 0.412f, 0.145f, 1.f},
+ {0.984f, 0.608f, 0.024f, 1.f},
+ {0.969f, 0.820f, 0.239f, 1.f},
+ {0.988f, 1.000f, 0.643f, 1.f},
+});
+
+// ----------------------------------------------------------------------------
+// [section] string comparator definition
+// ----------------------------------------------------------------------------
+bool StringComparator::operator()(const char *a, const char *b) const {
+ return strcmp(a, b) < 0;
+}
+
+// ----------------------------------------------------------------------------
+// [section] Trace (base) definition
+// ----------------------------------------------------------------------------
+template <class T>
+Trace<T>::Trace() : std::vector<T>() {}
+
+template <class T>
+Trace<T>::~Trace() {}
+
+template <class T>
+size_t Trace<T>::start_offset() const {
+ return (size_t)g_trace_offset;
+}
+
+template <class T>
+void Trace<T>::trim() {
+ trim_spec();
+}
+
+#if !defined(NDEBUG)
+template <class T>
+void Trace<T>::validate() const {
+ assert(this->size() == g_traces[0]->size());
+}
+#endif
+
+template <class T>
+void Trace<T>::clear() {
+ std::vector<T>::clear();
+}
+
+template <class T>
+void Trace<T>::push_back(T value) {
+ std::vector<T>::push_back(value);
+}
+
+template <class T>
+T *Trace<T>::start() {
+ return this->size() ? &operator[](start_offset()) : nullptr;
+}
+
+template <class T>
+T &Trace<T>::operator[](size_t n) {
+#if !defined(NDEBUG)
+ return this->at(n);
+#else
+ return std::vector<T>::operator[](n);
+#endif
+}
+
+template <class T>
+T Trace<T>::operator[](size_t n) const {
+#if !defined(NDEBUG)
+ return this->at(n);
+#else
+ return std::vector<T>::operator[](n);
+#endif
+}
+
+template <class T>
+void Trace<T>::trim_spec(int64_t multiplier) {
+ assert((int64_t)this->size() >= g_entries * multiplier * 2);
+ this->erase(this->begin(), this->end() - (g_entries * multiplier));
+}
+
+// ----------------------------------------------------------------------------
+// [section] TraceNamed definition
+// ----------------------------------------------------------------------------
+template <class T>
+TraceNamed<T>::TraceNamed(const char *name, const char *label) : m_name(name), m_label(label) {}
+
+template <class T>
+const char *TraceNamed<T>::get_name() const {
+ return m_name;
+}
+
+template <class T>
+const char *TraceNamed<T>::get_label() const {
+ return m_label;
+}
+
+// ----------------------------------------------------------------------------
+// [section] TraceHeatmap definition
+// ----------------------------------------------------------------------------
+template <class T>
+TraceHeatmap<T>::TraceHeatmap(const char *name, const char *name_fmt) : TraceNamed<T>(name, name_fmt) {}
+
+template <class T>
+size_t TraceHeatmap<T>::start_offset() const {
+ return (size_t)(g_trace_offset * g_hm_pixel_count);
+}
+
+template <class T>
+void TraceHeatmap<T>::trim() {
+ Trace<T>::trim_spec(g_hm_pixel_count);
+}
+
+#if !defined(NDEBUG)
+template <class T>
+void TraceHeatmap<T>::validate() const {
+ assert(this->size() == g_traces[0]->size() * g_hm_pixel_count);
+}
+#endif
+
+// ----------------------------------------------------------------------------
+// [section] Plot (base) definition
+// ----------------------------------------------------------------------------
+Plot::Plot(const char *name, const char *section) : m_visible(true), m_name(name), m_section(section) {}
+
+Plot::~Plot() {}
+
+void Plot::handle_input(int key, int mods) {
+ (void)key;
+ (void)mods;
+}
+
+void Plot::flag_for_reset() {}
+
+const char *Plot::get_name() const {
+ return m_name;
+}
+
+const char *Plot::get_section() const {
+ return m_section;
+}
+
+void Plot::render(const ImVec2 &frame_size) {
+ if (ImPlot::BeginPlot(m_name, ImVec2(frame_size.x - right_margin(), frame_size.y), flags())) {
+ int axis_flags = ImPlotAxisFlags_Foreground | (g_status != STATUS_STOPPED ? ImPlotAxisFlags_AutoFit : 0);
+ ImPlot::SetupAxes(nullptr, nullptr, axis_flags, axis_flags);
+ ImPlot::SetupAxisFormat(ImAxis_X1, x_formatter());
+ ImPlot::SetupAxisFormat(ImAxis_Y1, y_formatter());
+ if (ImPlot::IsPlotHovered()) g_plot_hovered = this;
+ render_internal(frame_size);
+ ImPlot::EndPlot();
+ }
+
+ render_post(frame_size);
+}
+
+int Plot::hex_formatter(double value, char *buff, int size, void *data) {
+ (void)data;
+ snprintf(buff, size, "%s%#lx", value < 0. ? "-" : "", abs((int64_t)value));
+ return 0;
+}
+
+AxisFormatter Plot::x_formatter() const {
+ return Plot::hex_formatter;
+}
+
+AxisFormatter Plot::y_formatter() const {
+ return Plot::hex_formatter;
+}
+
+float Plot::right_margin() const {
+ return 0.f;
+}
+
+int Plot::flags() const {
+ return 0;
+}
+
+void Plot::render_post(const ImVec2 &frame_size) {
+ (void)frame_size;
+}
+
+// ----------------------------------------------------------------------------
+// [section] PlotLines definition
+// ----------------------------------------------------------------------------
+PlotLines::PlotLines(const char *name, const char *section, std::vector<const char *> trace_keys) : Plot(name, section), m_trace_keys(trace_keys) {}
+
+void PlotLines::render_internal(const ImVec2 &frame_size) {
+ (void)frame_size;
+
+ ImS64 *x = g_trace_map[g_x_axes[g_x_axis]]->start();
+ ImPlotSpec spec = ImPlotSpec(ImPlotProp_Stride, sizeof(ImS64) * g_plot_stride);
+
+ for (auto &trace : m_trace_keys) {
+ TraceNamed<ImS64> *trace_obj = g_trace_map[trace];
+ ImS64 *y = trace_obj->start();
+ ImPlot::PlotLine(trace_obj->get_label(), x, y, g_downsampled_trace_len, spec);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] PlotStacked definition
+// ----------------------------------------------------------------------------
+PlotStacked::PlotStacked(const char *name, const char *section, std::vector<const char *> trace_keys) : Plot(name, section), m_trace_keys(trace_keys), m_trace_states(trace_keys.size(), true), m_trace_totals(), m_trace_normals(trace_keys.size()), m_needs_reset(true) {}
+
+void PlotStacked::flag_for_reset() {
+ m_needs_reset = true;
+}
+
+int PlotStacked::percent_formatter(double value, char *buff, int size, void *data) {
+ (void)data;
+ snprintf(buff, size, "%3.0f%%", value * 100.);
+ return 0;
+}
+
+AxisFormatter PlotStacked::y_formatter() const {
+ return PlotStacked::percent_formatter;
+}
+
+void PlotStacked::render_internal(const ImVec2 &frame_size) {
+ (void)frame_size;
+
+ for (size_t i = 0; i < m_trace_keys.size(); i++) {
+ ImPlot::PlotDummy(g_trace_map[m_trace_keys[i]]->get_label());
+ }
+
+ for (size_t i = 0; i < m_trace_keys.size(); i++) {
+ bool trace_visible = GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show;
+
+ if (m_trace_states[i] != trace_visible) {
+ m_trace_states[i] = trace_visible;
+ m_needs_reset = true;
+ }
+ }
+
+ if (m_needs_reset) {
+ m_trace_totals.clear();
+ for (auto &trace_normal : m_trace_normals) trace_normal.clear();
+ m_needs_reset = false;
+ }
+
+ size_t trace_size = m_trace_totals.size();
+
+ for (size_t i = trace_size; i < g_traces[0]->size(); i++) {
+ m_trace_totals.push_back(0l);
+
+ for (size_t j = 0; j < m_trace_keys.size(); j++) {
+ if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) {
+ m_trace_totals.back() += g_trace_map[m_trace_keys[j]]->operator[](i);
+ }
+ }
+ }
+
+ for (size_t i = trace_size; i < g_traces[0]->size(); i++) {
+ if (m_trace_totals[i]) {
+ double normal = 0.;
+
+ for (size_t j = 0; j < m_trace_keys.size(); j++) {
+ if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) {
+ normal += (double)g_trace_map[m_trace_keys[j]]->operator[](i) / (double)m_trace_totals[i];
+ m_trace_normals[j].push_back(normal);
+ }
+ }
+ } else {
+ for (size_t j = 0; j < m_trace_keys.size(); j++) {
+ if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) {
+ m_trace_normals[j].push_back(0.);
+ }
+ }
+ }
+ }
+
+#if !defined(NDEBUG)
+ m_trace_totals.validate();
+
+ for (size_t i = 0; i < m_trace_keys.size(); i++) {
+ if (GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show) {
+ m_trace_normals[i].validate();
+ } else {
+ assert(m_trace_normals[i].empty());
+ }
+ }
+#endif
+
+ Trace<double> *prev_trace = &g_zero_trace;
+
+ for (size_t i = 0; i < m_trace_keys.size(); i++) {
+ assert(m_trace_states[i] == GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show);
+
+ if (m_trace_states[i]) {
+ bool hovered = GImPlot->CurrentPlot->Items.GetLegendItem(i)->LegendHovered;
+ ImPlotSpec spec = ImPlotSpec(ImPlotProp_FillAlpha, hovered ? 1.f : 0.9f, ImPlotProp_Stride, sizeof(double) * g_plot_stride);
+ const char *trace_name = g_trace_map[m_trace_keys[i]]->get_label();
+ Trace<double> *current_trace = &m_trace_normals[i];
+ ImPlot::PlotShaded(trace_name, g_x_axis_double.start(), prev_trace->start(), current_trace->start(), g_downsampled_trace_len, spec);
+ ImPlot::PlotLine(trace_name, g_x_axis_double.start(), prev_trace->start(), g_downsampled_trace_len, spec);
+ if (hovered) ImPlot::PlotLine(trace_name, g_x_axis_double.start(), current_trace->start(), g_downsampled_trace_len, spec);
+ prev_trace = current_trace;
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] PlotHeatmap definition
+// ----------------------------------------------------------------------------
+PlotHeatmap::PlotHeatmap(const char *name, const char *section, const char *trace_key) : Plot(name, section), m_trace_key(trace_key), m_tex_scale_pow(-1.f), m_tex_scale_high(0.f), m_tex_rendered_last(0), m_rows_rendered(0), m_tex_id(0), m_tex_render(), m_needs_reset(true) {}
+
+void PlotHeatmap::handle_input(int key, int mods) {
+ switch (mods) {
+ case GLFW_MOD_CONTROL:
+ switch (key) {
+ case GLFW_KEY_K:
+ m_tex_scale_pow = std::clamp(m_tex_scale_pow - 1, HM_SCALE_POW_MIN, HM_SCALE_POW_MAX);
+ m_needs_reset = true;
+ break;
+ case GLFW_KEY_L:
+ m_tex_scale_pow = std::clamp(m_tex_scale_pow + 1, HM_SCALE_POW_MIN, HM_SCALE_POW_MAX);
+ m_needs_reset = true;
+ break;
+ }
+ }
+}
+
+void PlotHeatmap::flag_for_reset() {
+ m_needs_reset = true;
+}
+
+float PlotHeatmap::right_margin() const {
+ return HM_COLORSCALE_WIDTH;
+}
+
+int PlotHeatmap::flags() const {
+ return ImPlotFlags_NoLegend;
+}
+
+void PlotHeatmap::render_internal(const ImVec2 &frame_size) {
+ Trace<ImS64> *trace = g_trace_map[m_trace_key];
+
+ if (m_needs_reset) {
+ m_tex_scale_high = 0.f;
+ m_tex_rendered_last = trace->start_offset();
+ m_rows_rendered = 0;
+ glDeleteTextures(1, &m_tex_id);
+ m_tex_id = 0;
+ m_needs_reset = false;
+ }
+
+ if (m_tex_rendered_last == trace->size()) {
+ render_end(frame_size);
+ return;
+ }
+
+ assert(m_tex_render.empty());
+ assert((trace->size() - m_tex_rendered_last) % g_hm_pixel_count == 0);
+ size_t rows_to_append = (trace->size() - m_tex_rendered_last) / (size_t)g_hm_pixel_count;
+ size_t rows_to_chop_off = (m_rows_rendered + rows_to_append) > (size_t)g_trace_len ? (m_rows_rendered + rows_to_append) - (size_t)g_trace_len : 0;
+
+ if (rows_to_chop_off > m_rows_rendered) {
+ m_needs_reset = true;
+ render_internal(frame_size);
+ return;
+ }
+
+ if (m_tex_scale_pow == HM_SCALE_POW_MIN) {
+ ImS64 max = 0;
+
+ for (size_t i = trace->start_offset(); i < trace->size(); i++) {
+ max = std::max(max, trace->operator[](i));
+ }
+
+ m_tex_scale_high = (float)max;
+ } else {
+ m_tex_scale_high = pow(2.f, m_tex_scale_pow);
+ }
+
+ m_tex_render.reserve((trace->size() - m_tex_rendered_last) * 3);
+
+ if (m_tex_scale_high != 0.f) {
+ for (size_t i = m_tex_rendered_last; i < trace->size(); i++) {
+ ImS64 value = trace->operator[](i);
+ float normal = std::min((float)value / m_tex_scale_high, 1.f);
+ assert(normal >= 0.f && normal <= 1.f);
+ ImVec4 color = ImPlot::SampleColormap(normal, g_hm_colormap_id);
+ m_tex_render.push_back((uint8_t)(color.x * 255.f)); // red
+ m_tex_render.push_back((uint8_t)(color.y * 255.f)); // green
+ m_tex_render.push_back((uint8_t)(color.z * 255.f)); // blue
+ }
+ } else {
+ for (size_t i = m_tex_rendered_last; i < trace->size(); i++) {
+ m_tex_render.push_back(0);
+ m_tex_render.push_back(0);
+ m_tex_render.push_back(0);
+ }
+ }
+
+ GLuint new_tex_id;
+ glGenTextures(1, &new_tex_id);
+ glBindTexture(GL_TEXTURE_2D, new_tex_id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, g_hm_pixel_count, g_trace_len, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
+ assert(glGetError() == GL_NO_ERROR);
+ assert(new_tex_id);
+
+ if (m_tex_id) {
+ ((PFNGLCOPYIMAGESUBDATAPROC)glfwGetProcAddress("glCopyImageSubData"))(
+ m_tex_id, GL_TEXTURE_2D, 0, 0, rows_to_chop_off, 0,
+ new_tex_id, GL_TEXTURE_2D, 0, 0, 0, 0,
+ g_hm_pixel_count, m_rows_rendered - rows_to_chop_off, 1
+ );
+ assert(glGetError() == GL_NO_ERROR);
+ glDeleteTextures(1, &m_tex_id);
+ }
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, m_rows_rendered - rows_to_chop_off, g_hm_pixel_count, rows_to_append, GL_RGB, GL_UNSIGNED_BYTE, m_tex_render.data());
+ assert(glGetError() == GL_NO_ERROR);
+
+ render_end(frame_size);
+
+ m_tex_rendered_last = trace->size();
+ m_rows_rendered += rows_to_append - rows_to_chop_off;
+ assert(m_rows_rendered <= (size_t)g_trace_len);
+ m_tex_id = new_tex_id;
+ m_tex_render.clear();
+}
+
+void PlotHeatmap::render_end(const ImVec2 &frame_size) {
+ if (g_x_axis_double.empty()) return;
+
+ ImVec2 bmin(g_hm_left, (float)*g_x_axis_double.start());
+ ImVec2 bmax(g_hm_left + g_hm_pixel_count * pow(2.f, g_hm_pixel_pow), (float)g_x_axis_double.back());
+ ImVec2 uv0(0.f, 1.f);
+ ImVec2 uv1(1.f, 0.f);
+ double scale_max = m_tex_scale_pow == HM_SCALE_POW_MIN ? (double)m_tex_scale_high : pow(2., (double)m_tex_scale_pow);
+
+ ImPlot::PlotImage(get_name(), (ImTextureID)m_tex_id, bmin, bmax, uv0, uv1);
+ ImGui::SameLine();
+
+ ImPlot::PushColormap(g_hm_colormap_id);
+ ImPlot::ColormapScale("##hm-scale", 0., scale_max, ImVec2(HM_COLORSCALE_WIDTH, frame_size.y), "%.1e");
+ ImPlot::PopColormap();
+}
+
+void PlotHeatmap::render_post(const ImVec2 &frame_size) {
+ (void)frame_size;
+}
+
+// ----------------------------------------------------------------------------
+// [section] data functions
+// ----------------------------------------------------------------------------
+int64_t data_max_hm_pixel_pow(void) {
+ return (int64_t)floor(log2((double)(MVEC_SIZE - g_hm_left) / (double)g_hm_pixel_count));
+}
+
+void data_update_stride(void) {
+ if (g_plot_maximized) {
+ g_plot_stride = 1;
+ g_downsampled_trace_len = g_trace_len;
+ return;
+ }
+
+ int win_height = 0;
+ glfwGetWindowSize(g_window, nullptr, &win_height);
+
+ int max_stride = g_plot_cols * ((win_height / (int)g_plot_height) + 1);
+ int max_points = DEFVAL_ENTRIES / max_stride;
+ g_plot_stride = 1;
+
+ while (g_trace_len / g_plot_stride > max_points) {
+ g_plot_stride++;
+ }
+
+ g_downsampled_trace_len = g_trace_len / g_plot_stride;
+
+ log_info("Updated plot stride: %d", g_plot_stride);
+ log_info("Updated downsampled trace-len: %d", g_downsampled_trace_len);
+}
+
+void data_on_field_change(void) {
+ g_entries = std::clamp(g_entries, 1l, DEFVAL_ENTRIES);
+ g_nth = std::clamp(g_nth, DEFVAL_NTH, INT64_MAX);
+ g_x_low = std::clamp(g_x_low, DEFVAL_X_LOW, INT64_MAX);
+ g_x_high = std::clamp(g_x_high, g_x_low + 1l, DEFVAL_X_HIGH);
+#if !defined(MVEC_LOOP)
+ g_hm_left = std::clamp(g_hm_left, DEFVAL_HM_LEFT, (int64_t)MVEC_SIZE);
+#endif
+ g_hm_pixel_count = std::clamp(g_hm_pixel_count, 1l, DEFVAL_HM_PIXEL_COUNT);
+ g_hm_pixel_pow = std::clamp(g_hm_pixel_pow, 0l, data_max_hm_pixel_pow());
+ g_x_current = -1l;
+
+ g_trace_len = 0l;
+ g_trace_offset = 0;;
+ g_plot_stride = 0;
+ g_downsampled_trace_len = 0;
+
+ for (auto &trace : g_traces) trace->clear();
+ for (auto &plot: g_plots) plot->flag_for_reset();
+ g_x_axis_double.clear();
+ g_zero_trace.clear();
+}
+
+void data_reset_fields(void) {
+ g_entries = DEFVAL_ENTRIES;
+ g_nth = DEFVAL_NTH;
+ g_x_axis = DEFVAL_X_AXIS;
+ g_x_low = DEFVAL_X_LOW;
+ g_x_high = DEFVAL_X_HIGH;
+ g_hm_left = DEFVAL_HM_LEFT;
+ g_hm_pixel_count = DEFVAL_HM_PIXEL_COUNT;
+ g_hm_pixel_pow = data_max_hm_pixel_pow();
+
+ data_on_field_change();
+}
+
+void data_reset_plot_cells(void) {
+ std::fill(g_plot_cells.begin(), g_plot_cells.end(), nullptr);
+ std::fill(g_plot_cells_top.begin(), g_plot_cells_top.end(), 0.f);
+ std::fill(g_plot_cells_bottom.begin(), g_plot_cells_bottom.end(), 0.f);
+}
+
+void data_fetch(void) {
+ assert(g_status == STATUS_RUNNING);
+ g_status = STATUS_FETCHING;
+
+ json_object *request = json_object_new_object();
+ json_object_object_add(request, "request", json_object_new_string("data"));
+ json_object_object_add(request, "entries", json_object_new_int64(g_entries));
+ json_object_object_add(request, "nth", json_object_new_int64(g_nth));
+ json_object_object_add(request, "x-axis", json_object_new_string(g_x_axes[g_x_axis]));
+ json_object_object_add(request, "x-low", json_object_new_int64(g_x_low));
+ json_object_object_add(request, "x-high", json_object_new_int64(g_x_high));
+ json_object_object_add(request, "hm-left", json_object_new_int64(g_hm_left));
+ json_object_object_add(request, "hm-pixel-count", json_object_new_int64(g_hm_pixel_count));
+ json_object_object_add(request, "hm-pixel-pow", json_object_new_int64(g_hm_pixel_pow));
+ json_object_object_add(request, "x-current", json_object_new_int64(g_x_current));
+ const char *request_str = json_object_to_json_string(request);
+
+ log_info("Sending request to server: %s", request_str);
+ int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+ sockaddr_in socket_addr;
+ memset(&socket_addr, 0, sizeof(sockaddr_in));
+ socket_addr.sin_family = AF_INET;
+ socket_addr.sin_port = htons(PORT);
+ inet_pton(AF_INET, IP, &socket_addr.sin_addr);
+ if (connect(socket_fd, (sockaddr *)&socket_addr, sizeof(sockaddr_in))) assert(false);
+ json_object_to_fd(socket_fd, request, 0);
+ shutdown(socket_fd, SHUT_WR);
+
+ json_object *response = json_object_from_fd(socket_fd);
+
+ mtx_lock(&g_fetching_mutex);
+
+ json_object_object_foreach(response, key, value) {
+ size_t new_rows = json_object_array_length(value);
+
+ if (!strcmp(key, g_x_axes[g_x_axis])) {
+ log_info("Received %lu rows of data from server", new_rows);
+
+ for (size_t i = 0; i < new_rows; i++) {
+ ImS64 point = json_object_get_int64(json_object_array_get_idx(value, i));
+ g_x_axis_double.push_back((double)point);
+ g_zero_trace.push_back(0.);
+ }
+ }
+
+ if (g_trace_map.contains(key)) {
+ for (size_t i = 0; i < new_rows; i++) {
+ ImS64 point = json_object_get_int64(json_object_array_get_idx(value, i));
+ g_trace_map[key]->push_back(point);
+ }
+ }
+ }
+
+ g_x_current = g_trace_map[g_x_axes[g_x_axis]]->back();
+ json_object_put(request);
+ json_object_put(response);
+
+#if !defined(NDEBUG)
+ for (auto &trace : g_traces) trace->validate();
+ g_x_axis_double.validate();
+ g_zero_trace.validate();
+#endif
+
+ if ((int64_t)g_traces[0]->size() >= g_entries * 2) {
+ log_info("Trimming traces & flagging plots for reset");
+ for (auto &trace : g_traces) trace->trim();
+ for (auto &plot : g_plots) plot->flag_for_reset();
+ g_x_axis_double.trim();
+ g_zero_trace.trim();
+ }
+
+#if !defined(NDEBUG)
+ for (auto &trace : g_traces) trace->validate();
+ g_x_axis_double.validate();
+ g_zero_trace.validate();
+#endif
+
+ int64_t current_size = g_traces[0]->size();
+ g_trace_len = std::min(current_size, g_entries);
+ g_trace_offset = current_size > g_entries ? current_size - g_entries : 0l;
+ data_update_stride();
+
+ mtx_unlock(&g_fetching_mutex);
+ g_status = STATUS_RUNNING;
+}
+
+int data_fetching_thread(void *data) {
+ (void)data;
+
+ assert(!data);
+ assert(g_status == STATUS_RUNNING);
+
+ while (g_status == STATUS_RUNNING) {
+ data_fetch();
+
+ for (int i = 0; i < DATA_FETCH_INTERVAL_SUBDIV && g_status == STATUS_RUNNING; i++) {
+ usleep((DATA_FETCH_INTERVAL * 1000000) / DATA_FETCH_INTERVAL_SUBDIV);
+ }
+ }
+
+ assert(g_status == STATUS_STOPPING);
+ g_status = STATUS_STOPPED;
+ return 0;
+}
+
+void data_start_fetching(void) {
+ log_info("Starting data fetching thread");
+ g_status = STATUS_RUNNING;
+ thrd_create(&g_fetching_thread, (thrd_start_t)data_fetching_thread, nullptr);
+}
+
+void data_stop_fetching(void) {
+ assert(g_status == STATUS_RUNNING || g_status == STATUS_FETCHING);
+ log_info("Stopping data fetching thread");
+ g_status = STATUS_STOPPING;
+}
+
+// ----------------------------------------------------------------------------
+// [section] gui functions
+// ----------------------------------------------------------------------------
+void gui_render_data_input(const char *label, int64_t *target) {
+ assert(target);
+
+ if (ImGui::InputScalar(label, ImGuiDataType_U64, target, nullptr, nullptr, "%#lx")) {
+ data_on_field_change();
+ }
+}
+
+void gui_render_data_col(void) {
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ const ImVec2 win_pos = viewport->Pos;
+ const ImVec2 win_size = ImVec2(-1.f, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(win_pos);
+ ImGui::SetNextWindowSize(win_size);
+ ImGui::Begin("data-col", nullptr, IMGUI_WINDOW_FLAGS);
+ g_data_col_width = ImGui::GetWindowWidth();
+
+ ImGui::SeparatorText("SALIS data client");
+ ImGui::LabelText("name", NAME);
+ ImGui::LabelText("seed", "%#lx", SEED);
+ ImGui::LabelText("server", IP ":" PORT_STR);
+ ImGui::LabelText("arch", ARCH);
+ ImGui::LabelText("cores", "%d", CORES);
+ ImGui::LabelText("mvec-size", "%#lx", MVEC_SIZE);
+ #if defined(MVEC_LOOP)
+ ImGui::LabelText("mvec-loop", "true");
+ #else
+ ImGui::LabelText("mvec-loop", "false");
+ #endif
+ ImGui::LabelText("data-push", "%#lx", DATA_PUSH_INTERVAL);
+ ImGui::LabelText("fps", "%.1f", g_imgui_io->Framerate);
+
+ ImGui::SeparatorText("Data fields");
+
+ switch (g_status) {
+ case STATUS_STOPPED:
+ gui_render_data_input("entries", &g_entries);
+ gui_render_data_input("nth", &g_nth);
+
+ if (ImGui::BeginCombo("x-axis", g_x_axes[g_x_axis])) {
+ for (int i = 0; i < CORES + 2; i++) {
+ if (ImGui::Selectable(g_x_axes[i], g_x_axis == i)) {
+ data_reset_fields();
+ g_x_axis = i;
+ }
+ }
+
+ ImGui::EndCombo();
+ }
+
+ gui_render_data_input("x-low", &g_x_low);
+ gui_render_data_input("x-high", &g_x_high);
+ gui_render_data_input("hm-left", &g_hm_left);
+ gui_render_data_input("hm-pxl-count", &g_hm_pixel_count);
+ gui_render_data_input("hm-pxl-pow", &g_hm_pixel_pow);
+ break;
+ case STATUS_RUNNING:
+ case STATUS_FETCHING:
+ case STATUS_STOPPING:
+ ImGui::LabelText("entries", "%#lx", g_entries);
+ ImGui::LabelText("nth", "%#lx", g_nth);
+ ImGui::LabelText("x-axis", "%s", g_x_axes[g_x_axis]);
+ ImGui::LabelText("x-low", "%#lx", g_x_low);
+ ImGui::LabelText("x-high", "%#lx", g_x_high);
+ ImGui::LabelText("hm-left", "%#lx", g_hm_left);
+ ImGui::LabelText("hm-pxl-count", "%#lx", g_hm_pixel_count);
+ ImGui::LabelText("hm-pxl-pow", "%#lx", g_hm_pixel_pow);
+ }
+
+ switch (g_status) {
+ case STATUS_STOPPED:
+ if (ImGui::Button("Run", ImVec2(-1.f, 0.f))) data_start_fetching();
+ if (ImGui::Button("Reset", ImVec2(-1.f, 0.f))) data_reset_fields();
+ break;
+ case STATUS_RUNNING:
+ if (ImGui::Button("Stop", ImVec2(-1.f, 0.f))) data_stop_fetching();
+ ImGui::LabelText("##", "Running");
+ break;
+ case STATUS_FETCHING:
+ if (ImGui::Button("Stop", ImVec2(-1.f, 0.f))) data_stop_fetching();
+ ImGui::LabelText("##", "Fetching ...");
+ break;
+ case STATUS_STOPPING:
+ ImGui::BeginDisabled();
+ ImGui::Button("Stop", ImVec2(-1.f, 0.f));
+ ImGui::EndDisabled();
+ ImGui::LabelText("##", "Stopping");
+ break;
+ }
+
+ ImGui::SeparatorText("Layout");
+ ImGui::DragInt("cols", &g_plot_cols, 1, PLOT_MIN_COLS, PLOT_MAX_COLS);
+ ImGui::DragFloat("plot-height", &g_plot_height, PLOT_HEIGHT_INTERVAL, PLOT_MIN_HEIGHT, PLOT_MAX_HEIGHT, "%.0f");
+
+ ImGui::SeparatorText("Plots");
+ if (ImGui::Button("Show all", ImVec2(-1.f, 0.f))) for (auto &plot : g_plots) plot->m_visible = true;
+ if (ImGui::Button("Hide all", ImVec2(-1.f, 0.f))) for (auto &plot : g_plots) plot->m_visible = false;
+ ImGui::BeginTable("plot-visibility-table", 3);
+
+ for (auto &plot : g_plots) {
+ ImGui::TableNextColumn();
+ ImGui::Checkbox(plot->get_name(), &plot->m_visible);
+ }
+
+ ImGui::EndTable();
+ ImGui::End();
+}
+
+size_t gui_plot_cell_index(size_t row, size_t col) {
+ size_t index = row * PLOT_MAX_COLS + col;
+ assert(index < g_plot_cells.size());
+ assert(index < g_plot_cells_top.size());
+ assert(index < g_plot_cells_bottom.size());
+ return index;
+}
+
+size_t gui_plot_cell_row_up() {
+ for (size_t row = g_plot_row_selected - 1; row < g_plot_row_selected; row--) {
+ if (g_plot_cells[gui_plot_cell_index(row, g_plot_col_selected)]) {
+ return row;
+ }
+ }
+
+ return g_plot_row_selected;
+}
+
+size_t gui_plot_cell_row_down() {
+ for (size_t row = g_plot_row_selected + 1; row < g_plots.size(); row++) {
+ if (g_plot_cells[gui_plot_cell_index(row, g_plot_col_selected)]) {
+ return row;
+ }
+ }
+
+ return g_plot_row_selected;
+}
+
+void gui_render_plots(void) {
+ const char *section_current = g_plots[0]->get_section();
+ const char *section_next = nullptr;
+
+ g_plots_covered.clear();
+ g_plots_covered.resize(g_plots.size(), false);
+
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ const ImVec2 win_pos = g_data_col_visible ? ImVec2(g_data_col_width, viewport->Pos.y) : viewport->Pos;
+ const ImVec2 win_size = g_data_col_visible ? ImVec2(viewport->Size.x - g_data_col_width, -1.f) : ImVec2(viewport->Size.x, -1.f);
+
+ if (g_plot_scroll) {
+ ImGui::SetNextWindowScroll(ImVec2(-1.f, g_plot_scroll_to));
+ g_plot_scroll = false;
+ g_plot_scroll_to = 0.f;
+ }
+
+ ImGui::SetNextWindowPos(win_pos);
+ ImGui::SetNextWindowSize(win_size);
+ ImGui::Begin("plots", nullptr, IMGUI_WINDOW_FLAGS);
+ g_plot_scroll_current = ImGui::GetScrollY();
+ g_plot_hovered = nullptr;
+
+ if (!g_plot_selected->m_visible) {
+ g_plot_selected = g_plots[0];
+ g_plot_col_selected = 0;
+ g_plot_row_selected = 0;
+
+ for (auto &plot : g_plots) {
+ if (plot->m_visible) {
+ g_plot_selected = plot;
+ }
+ }
+ }
+
+ size_t row = 0;
+ size_t col = 0;
+
+ mtx_lock(&g_fetching_mutex);
+
+ while (section_current) {
+ ImGui::SeparatorText(section_current);
+ ImGui::BeginTable("plots-table", g_plot_cols);
+
+ for (size_t i = 0; i < g_plots.size(); i++) {
+ if (strcmp(g_plots[i]->get_section(), section_current)) {
+ section_next = (!section_next && !g_plots_covered[i]) ? g_plots[i]->get_section() : section_next;
+ continue;
+ }
+
+ if (g_plots[i]->m_visible) {
+ ImGui::TableNextColumn();
+ ImVec2 frame_size = ImVec2(ImGui::GetContentRegionAvail().x, g_plot_height);
+ g_plot_cells[gui_plot_cell_index(row, col)] = g_plots[i];
+ g_plot_cells_top[gui_plot_cell_index(row, col)] = ImGui::GetCursorPosY();
+
+ if (g_plots[i] == g_plot_selected) {
+ g_plot_col_selected = col;
+ g_plot_row_selected = row;
+ g_implot_style->Colors[ImPlotCol_FrameBg] = g_imgui_style->Colors[ImGuiCol_FrameBg];
+ }
+
+ g_plots[i]->render(frame_size);
+
+ if (g_plots[i] == g_plot_selected) {
+ g_implot_style->Colors[ImPlotCol_FrameBg] = COLOR_BLACK;
+ }
+
+ g_plot_cells_bottom[gui_plot_cell_index(row, col)] = ImGui::GetCursorPosY();
+ col = (col + 1) % g_plot_cols;
+ row += col ? 0 : 1;
+ }
+
+ g_plots_covered[i] = true;
+ }
+
+ section_current = section_next;
+ section_next = nullptr;
+ ImGui::EndTable();
+ row += col ? 1 : 0;
+ col = 0;
+ }
+
+ mtx_unlock(&g_fetching_mutex);
+ ImGui::End();
+}
+
+void gui_render_plot_maximized(void) {
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ ImGui::SetNextWindowPos(viewport->Pos);
+ ImGui::SetNextWindowSize(viewport->Size);
+ ImGui::Begin("plot-fullscreen", nullptr, IMGUI_WINDOW_FLAGS);
+
+ ImVec2 frame_size = {
+ viewport->Size.x - g_imgui_style->WindowPadding.x * 2,
+ viewport->Size.y - g_imgui_style->WindowPadding.y * 2,
+ };
+
+ mtx_lock(&g_fetching_mutex);
+ g_plot_selected->render(frame_size);
+ mtx_unlock(&g_fetching_mutex);
+ ImGui::End();
+}
+
+void gui_plot_queue_scroll_to_position(bool increased_plot_height) {
+ size_t row = 0;
+ float selected_plot_top = 0.f;
+
+ for (row = 0; row < g_plots.size(); row++) {
+ for (size_t col = 0; col < PLOT_MAX_COLS; col++) {
+ if (g_plot_selected == g_plot_cells[gui_plot_cell_index(row, col)]) {
+ selected_plot_top = g_plot_cells_top[gui_plot_cell_index(row, col)];
+ goto loop_exit;
+ }
+ }
+ }
+
+ loop_exit:
+ g_plot_scroll_to = selected_plot_top + (PLOT_HEIGHT_INTERVAL * (float)row * (increased_plot_height ? 1.f : -1.f)) - PLOT_SCROLL_MARGIN;
+ g_plot_scroll = true;
+}
+
+void gui_plot_queue_scroll_to_selected() {
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ float plot_top = g_plot_cells_top[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ float plot_bottom = g_plot_cells_bottom[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ float win_bottom = g_plot_scroll_current + viewport->Size.y;
+
+ if (plot_bottom > win_bottom) {
+ g_plot_scroll_to = g_plot_scroll_current + (plot_bottom - win_bottom);
+ g_plot_scroll = true;
+ }
+
+ if (plot_top < g_plot_scroll_current) {
+ g_plot_scroll_to = plot_top - PLOT_SCROLL_MARGIN;
+ g_plot_scroll = true;
+ }
+}
+
+void gui_render(void) {
+ if (g_plot_maximized) {
+ gui_render_plot_maximized();
+ return;
+ }
+
+ if (g_data_col_visible) gui_render_data_col();
+ gui_render_plots();
+}
+
+// ----------------------------------------------------------------------------
+// [section] main functions
+// ----------------------------------------------------------------------------
+void app_sig_handler(int signo) {
+ (void)signo;
+
+ log_warn("Signal received, will stop SALIS data client...");
+
+ if (g_status == STATUS_RUNNING || g_status == STATUS_FETCHING) {
+ data_stop_fetching();
+ }
+
+ glfwSetWindowShouldClose(g_window, GLFW_TRUE);
+}
+
+void app_error_callback(int error, const char* description) {
+ log_warn("GLFW error %d: %s", error, description);
+}
+
+void app_toggle_state() {
+ switch (g_status) {
+ case STATUS_STOPPED:
+ data_start_fetching();
+ break;
+ case STATUS_RUNNING:
+ case STATUS_FETCHING:
+ data_stop_fetching();
+ break;
+ }
+}
+
+void app_key_callback_plot_maximized(int key, int mods) {
+ switch (mods) {
+ case GLFW_MOD_CONTROL:
+ switch (key) {
+ case GLFW_KEY_C:
+ glfwSetWindowShouldClose(g_window, GLFW_TRUE);
+ break;
+ }
+
+ break;
+
+ case 0:
+ switch (key) {
+ case GLFW_KEY_F:
+ g_plot_maximized = false;
+ data_update_stride();
+ break;
+ case GLFW_KEY_SPACE:
+ app_toggle_state();
+ break;
+ }
+
+ break;
+ }
+
+ if (g_plot_selected->m_visible) {
+ g_plot_selected->handle_input(key, mods);
+ }
+}
+
+void app_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
+ (void)window;
+ (void)scancode;
+
+ if (ImGui::IsAnyItemActive()) {
+ return;
+ }
+
+ if (action != GLFW_PRESS && action != GLFW_REPEAT) {
+ return;
+ }
+
+ if (g_plot_maximized) {
+ app_key_callback_plot_maximized(key, mods);
+ return;
+ }
+
+ switch (mods) {
+ case GLFW_MOD_CONTROL:
+ switch (key) {
+ case GLFW_KEY_C:
+ glfwSetWindowShouldClose(g_window, GLFW_TRUE);
+ break;
+ case GLFW_KEY_N:
+ g_data_col_visible = !g_data_col_visible;
+ break;
+ case GLFW_KEY_LEFT:
+ g_plot_cols = std::max(g_plot_cols - 1, PLOT_MIN_COLS);
+ data_reset_plot_cells();
+ data_update_stride();
+ break;
+ case GLFW_KEY_RIGHT:
+ g_plot_cols = std::min(g_plot_cols + 1, PLOT_MAX_COLS);
+ data_reset_plot_cells();
+ data_update_stride();
+ break;
+ case GLFW_KEY_UP:
+ g_plot_height = std::min(g_plot_height + PLOT_HEIGHT_INTERVAL, PLOT_MAX_HEIGHT);
+ gui_plot_queue_scroll_to_position(true);
+ data_update_stride();
+ break;
+ case GLFW_KEY_DOWN:
+ g_plot_height = std::max(g_plot_height - PLOT_HEIGHT_INTERVAL, PLOT_MIN_HEIGHT);
+ gui_plot_queue_scroll_to_position(false);
+ data_update_stride();
+ break;
+ }
+
+ break;
+
+ case 0:
+ switch (key) {
+ case GLFW_KEY_LEFT:
+ g_plot_col_selected -= g_plot_col_selected ? 1 : 0;
+ g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ break;
+ case GLFW_KEY_RIGHT:
+ g_plot_col_selected += (g_plot_col_selected < PLOT_MAX_COLS - 1 && g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected + 1)]) ? 1 : 0;
+ g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ break;
+ case GLFW_KEY_UP:
+ g_plot_row_selected = gui_plot_cell_row_up();
+ g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ gui_plot_queue_scroll_to_selected();
+ break;
+ case GLFW_KEY_DOWN:
+ g_plot_row_selected = gui_plot_cell_row_down();
+ g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ gui_plot_queue_scroll_to_selected();
+ break;
+ case GLFW_KEY_F:
+ if (g_plot_selected->m_visible) {
+ g_plot_maximized = !g_plot_maximized;
+ data_update_stride();
+ }
+
+ break;
+ case GLFW_KEY_SPACE:
+ app_toggle_state();
+ break;
+ }
+
+ break;
+ }
+
+ if (g_plot_selected->m_visible) {
+ g_plot_selected->handle_input(key, mods);
+ }
+}
+
+void app_window_size_callback(GLFWwindow* window, int width, int height) {
+ (void)window;
+ (void)width;
+ (void)height;
+
+ data_update_stride();
+}
+
+void app_mouse_button_callback(GLFWwindow* window, int button, int action, int mods) {
+ (void)window;
+ (void)mods;
+
+ if (button != GLFW_MOUSE_BUTTON_LEFT) return;
+
+ switch (action) {
+ case GLFW_PRESS:
+ g_plot_selected = g_plot_hovered ? g_plot_hovered : g_plot_selected;
+ break;
+ }
+}
+
+void init() {
+ signal(SIGINT, app_sig_handler);
+ signal(SIGTERM, app_sig_handler);
+
+ log_info("Starting SALIS data client");
+
+ log_info("Initializing GLFW");
+ glfwSetErrorCallback(app_error_callback);
+ glfwInitHint(GLFW_WAYLAND_LIBDECOR, GLFW_WAYLAND_DISABLE_LIBDECOR);
+ if (!glfwInit()) assert(false);
+
+ float scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor());
+ g_window = glfwCreateWindow((int)(800 * scale), (int)(600 * scale), "SALIS data client", nullptr, nullptr);
+ assert(g_window);
+ glfwSetKeyCallback(g_window, app_key_callback);
+ glfwSetMouseButtonCallback(g_window, app_mouse_button_callback);
+ glfwSetWindowSizeCallback(g_window, app_window_size_callback);
+ glfwMakeContextCurrent(g_window);
+#if defined(VSYNC)
+ glfwSwapInterval(1);
+#else
+ glfwSwapInterval(0);
+#endif
+
+ log_info("Initializing ImGui");
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImPlot::CreateContext();
+
+ g_imgui_io = &ImGui::GetIO();
+ g_imgui_io->Fonts->AddFontFromFileTTF(FONT_SOURCE, FONT_SIZE);
+ g_imgui_io->IniFilename = nullptr;
+
+ g_imgui_style = &ImGui::GetStyle();
+ g_imgui_style->Colors[ImGuiCol_WindowBg] = COLOR_BLACK;
+ g_imgui_style->FontScaleDpi = scale;
+ g_imgui_style->FontSizeBase = FONT_SIZE;
+ g_imgui_style->ItemSpacing = ImVec2(g_imgui_style->ItemSpacing.x, 2.f);
+ g_imgui_style->ScaleAllSizes(scale);
+
+ g_implot_style = &ImPlot::GetStyle();
+ g_implot_style->Colors[ImPlotCol_FrameBg] = COLOR_BLACK;
+ g_hm_colormap_id = ImPlot::AddColormap("heatmap", g_hm_colormap.data(), g_hm_colormap.size(), false);
+
+ ImGui_ImplGlfw_InitForOpenGL(g_window, true);
+ ImGui_ImplOpenGL3_Init(GLSL_VERSION);
+
+ for (auto &trace : g_core_traces) g_traces.push_back(&trace);
+ for (auto &trace : g_arch_traces) g_traces.push_back(&trace);
+ for (auto &trace : g_core_traces_heatmaps) g_traces.push_back(&trace);
+ for (auto &trace : g_arch_traces_heatmaps) g_traces.push_back(&trace);
+ for (auto &plot : g_core_plots) g_plots.push_back(&plot);
+ for (auto &plot : g_arch_plots) g_plots.push_back(&plot);
+ for (auto &plot : g_arch_plots_stacked) g_plots.push_back(&plot);
+ for (auto &plot : g_core_plots_heatmaps) g_plots.push_back(&plot);
+ for (auto &plot : g_arch_plots_heatmaps) g_plots.push_back(&plot);
+ for (auto &i : g_traces) g_trace_map[i->get_name()] = i;
+
+ g_plot_cells = std::vector<Plot *>(g_plots.size() * PLOT_MAX_COLS);
+ g_plot_cells_top = std::vector<float>(g_plots.size() * PLOT_MAX_COLS);
+ g_plot_cells_bottom = std::vector<float>(g_plots.size() * PLOT_MAX_COLS);
+ g_plot_selected = g_plots[0];
+
+ g_hm_pixel_pow = data_max_hm_pixel_pow();
+ data_update_stride();
+
+ mtx_init(&g_fetching_mutex, mtx_plain);
+}
+
+void exec() {
+ while (!glfwWindowShouldClose(g_window)) {
+ glfwPollEvents();
+
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ gui_render();
+
+ ImGui::Render();
+ int display_w;
+ int display_h;
+ glfwGetFramebufferSize(g_window, &display_w, &display_h);
+ glViewport(0, 0, display_w, display_h);
+ glClearColor(0.f, 0.f, 0.f, 1.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ glfwSwapBuffers(g_window);
+ }
+}
+
+void quit() {
+ mtx_destroy(&g_fetching_mutex);
+
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplGlfw_Shutdown();
+ ImPlot::DestroyContext();
+ ImGui::DestroyContext();
+
+ log_info("Stopping SALIS data client");
+ glfwDestroyWindow(g_window);
+ glfwTerminate();
+}
+
+int main(int argc, char **argv) {
+ (void)argc;
+ (void)argv;
+
+ init();
+ exec();
+ quit();
+
+ return 0;
+}
diff --git a/core/common.c b/core/common.c
new file mode 100644
index 0000000..786ab38
--- /dev/null
+++ b/core/common.c
@@ -0,0 +1,3 @@
+#define DEFVAL_HM_PIXEL_COUNT 0x400l
+#define EVENT_ARRAYS_SIZE (sizeof(uint64_t) * MVEC_SIZE)
+#define EVENT_ARRAYS_SIZE_COL_MARKER "_evasize_"
diff --git a/core/compress.c b/core/compress.c
new file mode 100644
index 0000000..e457de5
--- /dev/null
+++ b/core/compress.c
@@ -0,0 +1,79 @@
+struct DeflateParams {
+ z_stream strm;
+ size_t size;
+ Bytef *in;
+ Bytef *out;
+#if defined(THREAD_GAP)
+ uint8_t tgap[THREAD_GAP];
+#endif
+};
+
+struct InflateParams {
+ z_stream strm;
+ size_t avail_in;
+ size_t size;
+ Bytef *in;
+ Bytef *out;
+#if defined(THREAD_GAP)
+ uint8_t tgap[THREAD_GAP];
+#endif
+};
+
+int comp_deflate(struct DeflateParams *params) {
+ assert(params);
+ assert(params->size);
+ assert(params->in);
+ assert(params->out);
+
+ params->strm.zalloc = NULL;
+ params->strm.zfree = NULL;
+ params->strm.opaque = NULL;
+
+ deflateInit(&params->strm, Z_DEFAULT_COMPRESSION);
+
+ params->strm.avail_in = params->size;
+ params->strm.avail_out = params->size;
+ params->strm.next_in = params->in;
+ params->strm.next_out = params->out;
+
+ deflate(&params->strm, Z_FINISH);
+
+ return 0;
+}
+
+void comp_deflate_end(struct DeflateParams *params) {
+ assert(params);
+ deflateEnd(&params->strm);
+}
+
+int comp_inflate(struct InflateParams *params) {
+ assert(params);
+ assert(params->avail_in);
+ assert(params->size);
+ assert(params->in);
+ assert(params->out);
+
+ params->strm.next_in = params->in;
+ params->strm.avail_in = params->avail_in;
+ params->strm.zalloc = NULL;
+ params->strm.zfree = NULL;
+ params->strm.opaque = NULL;
+
+ inflateInit(&params->strm);
+
+ params->strm.avail_out = params->size;
+ params->strm.next_out = params->out;
+
+#if defined(NDEBUG)
+ inflate(&params->strm, Z_FINISH);
+#else
+ assert(inflate(&params->strm, Z_FINISH));
+#endif
+
+ return 0;
+}
+
+void comp_inflate_end(struct InflateParams *params) {
+ assert(params);
+ inflateEnd(&params->strm);
+}
diff --git a/core/logger.c b/core/logger.c
new file mode 100644
index 0000000..af41dcc
--- /dev/null
+++ b/core/logger.c
@@ -0,0 +1,105 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+#define LOG_LINE_SIZE 1024
+
+enum LogLevel {
+ INFO,
+ WARN,
+};
+
+void log_msg_to_buff(char *out, int size, enum LogLevel level, bool colored, const char *format, va_list args) {
+ assert(out);
+ assert(size);
+ assert(level == INFO || level == WARN);
+ assert(format);
+
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ long msec = ts.tv_nsec / 1000000;
+ struct tm tm = *localtime(&ts.tv_sec);
+ pid_t pid = getpid();
+ const char *level_str = NULL;
+
+ switch (level) {
+ case INFO:
+ level_str = "INFO";
+ break;
+ case WARN:
+ level_str = "WARN";
+ break;
+ default:
+ assert(false);
+ }
+
+ const char *color_code = NULL;
+ if (colored) {
+ switch (level) {
+ case INFO:
+ color_code = "\033[1;32m";
+ break;
+ case WARN:
+ color_code = "\033[1;33m";
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ // Imitate formatting style configured in 'salis.py'
+ int col = snprintf(
+ out,
+ size,
+ "%s%d-%02d-%02d %02d:%02d:%02d,%03ld %07d [%s]%s ",
+ colored ? color_code : "",
+ tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ msec,
+ pid,
+ level_str,
+ colored ? "\033[0m" : ""
+ );
+
+ vsnprintf(out + col, size - col, format, args);
+}
+
+void log_msg(enum LogLevel level, bool colored, const char *format, va_list args) {
+ assert(level == INFO || level == WARN);
+ assert(format);
+
+ char buff[LOG_LINE_SIZE];
+ log_msg_to_buff(buff, LOG_LINE_SIZE, level, colored, format, args);
+ printf("\r%s\n", buff);
+ fflush(stdout);
+}
+
+void log_info_default(const char *format, ...) {
+ assert(format);
+
+ va_list args;
+ va_start(args, format);
+ log_msg(INFO, true, format, args);
+ va_end(args);
+}
+
+void log_warn_default(const char *format, ...) {
+ assert(format);
+
+ va_list args;
+ va_start(args, format);
+ log_msg(WARN, true, format, args);
+ va_end(args);
+}
+
+// Client may install their own loggers
+void (*log_info)(const char *fmt, ...) = log_info_default;
+void (*log_warn)(const char *fmt, ...) = log_warn_default;
diff --git a/core/salis.c b/core/salis.c
new file mode 100644
index 0000000..11c6efb
--- /dev/null
+++ b/core/salis.c
@@ -0,0 +1,1139 @@
+// index
+// [section] includes
+// [section] macros & enums
+// [section] structs
+// [section] globals
+// [section] architecture forward declarations
+// [section] memory vector functions
+// [section] mutator functions
+// [section] process functions
+// [section] core functions
+// [section] salis functions
+// [section] architecture & ui includes
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <assert.h>
+#include <sqlite3.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <threads.h>
+#include <zlib.h>
+
+#include "common.c"
+#include "compress.c"
+#include "logger.c"
+#include "sql.c"
+
+// ----------------------------------------------------------------------------
+// [section] macros & enums
+// ----------------------------------------------------------------------------
+#define INST_CAP 0x80
+#define INST_MASK 0x7f
+#define IPC_FLAG 0x80
+#define MALL_FLAG 0x80
+#define UINT64_HALF 0x8000000000000000ul
+
+#define EVENT_ARRAYS(core) \
+ EVENT_ARRAY(core, 0, aev) /* allocation events array */ \
+ EVENT_ARRAY(core, 1, eev) /* executions events array */ \
+ EVENT_ARRAY(core, 2, bev) /* birth events array */
+#define EVENT_ARRAYS_COUNT 3
+
+// ----------------------------------------------------------------------------
+// [section] structs
+// ----------------------------------------------------------------------------
+struct Proc {
+#define PROC_FIELD(type, name) type name;
+ PROC_FIELDS
+#undef PROC_FIELD
+};
+
+struct Core {
+ uint64_t cycl;
+ uint64_t mall;
+ uint64_t muta[4];
+
+ uint64_t pnum;
+ uint64_t pcap;
+ uint64_t pfst;
+ uint64_t plst;
+ uint64_t pcur;
+ uint64_t psli;
+
+ thrd_t thrd;
+ uint64_t thrd_steps;
+
+ uint64_t ivpt;
+ uint64_t *ivav;
+ uint8_t *iviv;
+
+ uint64_t emb0; // executions within mb0 counter
+ uint64_t emb1; // executions within mb1 counter
+ uint64_t eliv; // executions within not-owned live code counter (parasites)
+ uint64_t edea; // executions within dead code counter
+
+#define EVENT_ARRAY(core, index, ev) uint64_t ev##a[MVEC_SIZE];
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+
+#define CORE_DATA_FIELD(type, name) type name;
+ CORE_DATA_FIELDS
+#undef CORE_DATA_FIELD
+
+#define CORE_FIELD(type, name) type name;
+ CORE_FIELDS
+#undef CORE_FIELD
+
+ struct Proc *pvec;
+ uint8_t mvec[MVEC_SIZE];
+ uint8_t tgap[THREAD_GAP];
+};
+
+// ----------------------------------------------------------------------------
+// [section] globals
+// ----------------------------------------------------------------------------
+struct Core g_cores[CORES];
+uint64_t g_steps;
+uint64_t g_syncs;
+const struct Proc g_dead_proc;
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+char g_asav_pbuf[AUTOSAVE_NAME_LEN];
+char g_eva_pbuf[EVA_SAVE_NAME_LEN];
+#endif
+
+thrd_t g_eva_thrds[CORES][EVENT_ARRAYS_COUNT];
+struct DeflateParams g_eva_deflate_params[CORES][EVENT_ARRAYS_COUNT];
+
+// ----------------------------------------------------------------------------
+// [section] architecture forward declarations
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW)
+void arch_core_init(struct Core *core);
+#endif
+
+void arch_core_free(struct Core *core);
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+void arch_core_save(FILE *f, const struct Core *core);
+#endif
+
+#if defined(COMMAND_LOAD)
+void arch_core_load(FILE *f, struct Core *core);
+#endif
+
+uint64_t arch_proc_mb0_addr(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_mb0_size(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_mb1_addr(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_mb1_size(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_ip_addr(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_sp_addr(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_slice(const struct Core *core, uint64_t pix);
+void arch_on_proc_kill(struct Core *core);
+void arch_proc_step(struct Core *core, uint64_t pix);
+
+#if !defined(NDEBUG)
+void arch_validate_proc(const struct Core *core, uint64_t pix);
+#endif
+
+wchar_t arch_symbol(uint8_t inst);
+const char *arch_mnemonic(uint8_t inst);
+
+#if defined(COMMAND_NEW)
+void arch_push_data_header(void);
+#endif
+void arch_push_data_line(FILE *eva_file);
+
+// ----------------------------------------------------------------------------
+// [section] memory vector functions
+// ----------------------------------------------------------------------------
+#if defined(MVEC_LOOP)
+uint64_t mvec_loop(uint64_t addr) {
+ return addr % MVEC_SIZE;
+}
+#endif
+
+bool mvec_is_alloc(const struct Core *core, uint64_t addr) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ return core->mvec[mvec_loop(addr)] & MALL_FLAG ? true : false;
+#else
+ if (addr < MVEC_SIZE) {
+ return core->mvec[addr] & MALL_FLAG ? true : false;
+ } else {
+ return true;
+ }
+#endif
+}
+
+void mvec_alloc(struct Core *core, uint64_t addr) {
+ assert(core);
+ assert(!mvec_is_alloc(core, addr));
+
+#if defined(MVEC_LOOP)
+ core->mvec[mvec_loop(addr)] |= MALL_FLAG;
+
+ // Record deallocation event
+ ++core->aeva[mvec_loop(addr)];
+#else
+ assert(addr < MVEC_SIZE);
+ core->mvec[addr] |= MALL_FLAG;
+
+ // Record deallocation event
+ ++core->aeva[addr];
+#endif
+ core->mall++;
+}
+
+void mvec_free(struct Core *core, uint64_t addr) {
+ assert(core);
+ assert(mvec_is_alloc(core, addr));
+
+#if defined(MVEC_LOOP)
+ core->mvec[mvec_loop(addr)] ^= MALL_FLAG;
+
+ // Record deallocation event
+ ++core->aeva[mvec_loop(addr)];
+#else
+ assert(addr < MVEC_SIZE);
+ core->mvec[addr] ^= MALL_FLAG;
+
+ // Record deallocation event
+ ++core->aeva[addr];
+#endif
+ core->mall--;
+}
+
+uint8_t mvec_get_byte(const struct Core *core, uint64_t addr) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ return core->mvec[mvec_loop(addr)];
+#else
+ if (addr < MVEC_SIZE) {
+ return core->mvec[addr];
+ } else {
+ return 0;
+ }
+#endif
+}
+
+uint8_t mvec_get_inst(const struct Core *core, uint64_t addr) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ return core->mvec[mvec_loop(addr)] & INST_MASK;
+#else
+ if (addr < MVEC_SIZE) {
+ return core->mvec[addr] & INST_MASK;
+ } else {
+ return 0;
+ }
+#endif
+}
+
+void mvec_set_inst(struct Core *core, uint64_t addr, uint8_t inst) {
+ assert(core);
+ assert(inst < INST_CAP);
+
+#if defined(MVEC_LOOP)
+ core->mvec[mvec_loop(addr)] &= MALL_FLAG;
+ core->mvec[mvec_loop(addr)] |= inst;
+#else
+ assert(addr < MVEC_SIZE);
+ core->mvec[addr] &= MALL_FLAG;
+ core->mvec[addr] |= inst;
+#endif
+}
+
+#if defined(MUTA_FLIP)
+void mvec_flip_bit(struct Core *core, uint64_t addr, int bit) {
+ assert(core);
+ assert(bit < 8);
+ core->mvec[addr] ^= (1 << bit) & INST_MASK;
+}
+#endif
+
+bool mvec_proc_is_live(const struct Core *core, uint64_t pix) {
+ assert(core);
+
+ return pix >= core->pfst && pix <= core->plst;
+}
+
+bool mvec_is_in_mb0_of_proc(const struct Core *core, uint64_t addr, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ uint64_t mb0a = arch_proc_mb0_addr(core, pix);
+ uint64_t mb0s = arch_proc_mb0_size(core, pix);
+
+ return ((addr - mb0a) % MVEC_SIZE) < mb0s;
+}
+
+bool mvec_is_in_mb1_of_proc(const struct Core *core, uint64_t addr, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ uint64_t mb1a = arch_proc_mb1_addr(core, pix);
+ uint64_t mb1s = arch_proc_mb1_size(core, pix);
+
+ return ((addr - mb1a) % MVEC_SIZE) < mb1s;
+}
+
+bool mvec_is_proc_owner(const struct Core *core, uint64_t addr, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return mvec_is_in_mb0_of_proc(core, addr, pix) || mvec_is_in_mb1_of_proc(core, addr, pix);
+}
+
+uint64_t mvec_get_owner(const struct Core *core, uint64_t addr) {
+ assert(core);
+#if !defined(MVEC_LOOP)
+ assert(addr < MVEC_SIZE);
+#endif
+ assert(mvec_is_alloc(core, addr));
+
+ for (uint64_t pix = core->pfst; pix <= core->plst; ++pix) {
+ if (mvec_is_proc_owner(core, addr, pix)) {
+ return pix;
+ }
+ }
+
+ assert(false);
+ return -1;
+}
+
+// ----------------------------------------------------------------------------
+// [section] mutator functions
+// ----------------------------------------------------------------------------
+#if SEED != 0
+#if defined(COMMAND_NEW)
+uint64_t muta_smix(uint64_t *seed) {
+ assert(seed);
+
+ uint64_t next = (*seed += 0x9e3779b97f4a7c15);
+ next = (next ^ (next >> 30)) * 0xbf58476d1ce4e5b9;
+ next = (next ^ (next >> 27)) * 0x94d049bb133111eb;
+
+ return next ^ (next >> 31);
+}
+#endif
+
+uint64_t muta_ro64(uint64_t x, int k) {
+ return (x << k) | (x >> (64 - k));
+}
+
+uint64_t muta_next(struct Core *core) {
+ assert(core);
+
+ uint64_t r = muta_ro64(core->muta[1] * 5, 7) * 9;
+ uint64_t t = core->muta[1] << 17;
+
+ core->muta[2] ^= core->muta[0];
+ core->muta[3] ^= core->muta[1];
+ core->muta[1] ^= core->muta[2];
+ core->muta[0] ^= core->muta[3];
+
+ core->muta[2] ^= t;
+ core->muta[3] = muta_ro64(core->muta[3], 45);
+
+ return r;
+}
+
+void muta_cosmic_ray(struct Core *core) {
+ assert(core);
+
+ uint64_t a = muta_next(core) % MUTA_RANGE;
+ uint64_t b = muta_next(core);
+
+ if (a < MVEC_SIZE) {
+#if defined(MUTA_FLIP)
+ mvec_flip_bit(core, a, (int)(b % 8));
+#else
+ mvec_set_inst(core, a, b & INST_MASK);
+#endif
+ }
+}
+#endif
+
+// ----------------------------------------------------------------------------
+// [section] process functions
+// ----------------------------------------------------------------------------
+void proc_new(struct Core *core, const struct Proc *proc) {
+ assert(core);
+ assert(proc);
+
+ if (core->pnum == core->pcap) {
+ // Reallocate dynamic array
+ uint64_t new_pcap = core->pcap * 2;
+ struct Proc *new_pvec = calloc(new_pcap, sizeof(struct Proc));
+
+ for (uint64_t pix = core->pfst; pix <= core->plst; ++pix) {
+ uint64_t iold = pix % core->pcap;
+ uint64_t inew = pix % new_pcap;
+ memcpy(&new_pvec[inew], &core->pvec[iold], sizeof(struct Proc));
+ }
+
+ free(core->pvec);
+ core->pcap = new_pcap;
+ core->pvec = new_pvec;
+ }
+
+ core->pnum++;
+ core->plst++;
+ memcpy(&core->pvec[core->plst % core->pcap], proc, sizeof(struct Proc));
+
+ // Store birth event in database
+ uint64_t child_addr = arch_proc_mb0_addr(core, core->plst);
+ uint64_t child_size = arch_proc_mb0_size(core, core->plst);
+
+ for (uint64_t i = 0; i < child_size; i++) {
+ uint64_t addr = child_addr + i;
+#if defined(MVEC_LOOP)
+ ++core->beva[mvec_loop(addr)];
+#else
+ ++core->beva[addr];
+#endif
+ }
+}
+
+void proc_kill(struct Core *core) {
+ assert(core);
+ assert(core->pnum > 1);
+
+ arch_on_proc_kill(core);
+
+ core->pcur++;
+ core->pfst++;
+ core->pnum--;
+}
+
+const struct Proc *proc_get(const struct Core *core, uint64_t pix) {
+ assert(core);
+
+ if (mvec_proc_is_live(core, pix)) {
+ return &core->pvec[pix % core->pcap];
+ } else {
+ return &g_dead_proc;
+ }
+}
+
+struct Proc *proc_fetch(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ return &core->pvec[pix % core->pcap];
+}
+
+// ----------------------------------------------------------------------------
+// [section] core functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+void core_save(FILE *f, const struct Core *core) {
+ assert(f);
+ assert(core);
+
+ fwrite(&core->cycl, sizeof(uint64_t), 1, f);
+ fwrite(&core->mall, sizeof(uint64_t), 1, f);
+ fwrite(core->muta, sizeof(uint64_t), 4, f);
+ fwrite(&core->pnum, sizeof(uint64_t), 1, f);
+ fwrite(&core->pcap, sizeof(uint64_t), 1, f);
+ fwrite(&core->pfst, sizeof(uint64_t), 1, f);
+ fwrite(&core->plst, sizeof(uint64_t), 1, f);
+ fwrite(&core->pcur, sizeof(uint64_t), 1, f);
+ fwrite(&core->psli, sizeof(uint64_t), 1, f);
+ fwrite(&core->ivpt, sizeof(uint64_t), 1, f);
+ fwrite(&core->emb0, sizeof(uint64_t), 1, f);
+ fwrite(&core->emb1, sizeof(uint64_t), 1, f);
+ fwrite(&core->eliv, sizeof(uint64_t), 1, f);
+ fwrite(&core->edea, sizeof(uint64_t), 1, f);
+
+ fwrite(core->iviv, sizeof(uint8_t), SYNC_INTERVAL, f);
+ fwrite(core->ivav, sizeof(uint64_t), SYNC_INTERVAL, f);
+ fwrite(core->pvec, sizeof(struct Proc), core->pcap, f);
+ fwrite(core->mvec, sizeof(uint8_t), MVEC_SIZE, f);
+#define EVENT_ARRAY(core, index, ev) \
+ fwrite(core->ev##a, sizeof(uint64_t), MVEC_SIZE, f);
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+
+ arch_core_save(f, core);
+}
+#endif
+
+#if defined(COMMAND_NEW)
+#if defined(ANC_BYTES)
+void core_assemble_ancestor(struct Core *core) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ uint64_t addr = UINT64_HALF;
+#else
+ uint64_t addr = 0;
+#endif
+
+ uint8_t anc_bytes[] = ANC_BYTES;
+
+ for (uint64_t i = 0; i < sizeof(anc_bytes); ++i, ++addr) {
+ for (uint64_t j = 0; j < CLONES; ++j) {
+ uint64_t addr_clone = addr + (MVEC_SIZE / CLONES) * j;
+
+ mvec_alloc(core, addr_clone);
+ mvec_set_inst(core, addr_clone, anc_bytes[i]);
+ }
+ }
+}
+#endif
+
+void core_init(struct Core *core, uint64_t *seed) {
+ assert(core);
+ assert(seed);
+
+#if SEED != 0
+ assert(*seed);
+ core->muta[0] = muta_smix(seed);
+ core->muta[1] = muta_smix(seed);
+ core->muta[2] = muta_smix(seed);
+ core->muta[3] = muta_smix(seed);
+#else
+ (void)seed;
+#endif
+
+ core->pnum = CLONES;
+ core->pcap = CLONES;
+ core->plst = CLONES - 1;
+ core->pcur = CLONES - 1;
+ core->iviv = calloc(SYNC_INTERVAL, sizeof(uint8_t));
+ core->ivav = calloc(SYNC_INTERVAL, sizeof(uint64_t));
+ core->pvec = calloc(core->pcap, sizeof(struct Proc));
+
+ assert(core->iviv);
+ assert(core->ivav);
+ assert(core->pvec);
+
+#if defined(ANC_BYTES)
+ core_assemble_ancestor(core);
+ arch_core_init(core);
+#endif
+}
+#endif
+
+#if defined(COMMAND_LOAD)
+void core_load(FILE *f, struct Core *core) {
+ assert(f);
+ assert(core);
+
+ fread(&core->cycl, sizeof(uint64_t), 1, f);
+ fread(&core->mall, sizeof(uint64_t), 1, f);
+ fread(core->muta, sizeof(uint64_t), 4, f);
+ fread(&core->pnum, sizeof(uint64_t), 1, f);
+ fread(&core->pcap, sizeof(uint64_t), 1, f);
+ fread(&core->pfst, sizeof(uint64_t), 1, f);
+ fread(&core->plst, sizeof(uint64_t), 1, f);
+ fread(&core->pcur, sizeof(uint64_t), 1, f);
+ fread(&core->psli, sizeof(uint64_t), 1, f);
+ fread(&core->ivpt, sizeof(uint64_t), 1, f);
+ fread(&core->emb0, sizeof(uint64_t), 1, f);
+ fread(&core->emb1, sizeof(uint64_t), 1, f);
+ fread(&core->eliv, sizeof(uint64_t), 1, f);
+ fread(&core->edea, sizeof(uint64_t), 1, f);
+
+ core->iviv = calloc(SYNC_INTERVAL, sizeof(uint8_t));
+ core->ivav = calloc(SYNC_INTERVAL, sizeof(uint64_t));
+ core->pvec = calloc(core->pcap, sizeof(struct Proc));
+
+ assert(core->iviv);
+ assert(core->ivav);
+ assert(core->pvec);
+
+ fread(core->iviv, sizeof(uint8_t), SYNC_INTERVAL, f);
+ fread(core->ivav, sizeof(uint64_t), SYNC_INTERVAL, f);
+ fread(core->pvec, sizeof(struct Proc), core->pcap, f);
+ fread(core->mvec, sizeof(uint8_t), MVEC_SIZE, f);
+#define EVENT_ARRAY(core, index, ev) \
+ fread(core->ev##a, sizeof(uint64_t), MVEC_SIZE, f);
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+
+ arch_core_load(f, core);
+}
+#endif
+
+void core_pull_ipcm(struct Core *core) {
+ assert(core);
+ assert(core->ivpt < SYNC_INTERVAL);
+
+ uint8_t *iinst = &core->iviv[core->ivpt];
+ uint64_t *iaddr = &core->ivav[core->ivpt];
+
+ if ((*iinst & IPC_FLAG) != 0) {
+ mvec_set_inst(core, *iaddr, *iinst & INST_MASK);
+
+ *iinst = 0;
+ *iaddr = 0;
+ }
+
+ assert(*iinst == 0);
+ assert(*iaddr == 0);
+}
+
+void core_push_ipcm(struct Core *core, uint8_t inst, uint64_t addr) {
+ assert(core);
+ assert(core->ivpt < SYNC_INTERVAL);
+ assert((inst & IPC_FLAG) == 0);
+
+ uint8_t *iinst = &core->iviv[core->ivpt];
+ uint64_t *iaddr = &core->ivav[core->ivpt];
+
+ assert(*iinst == 0);
+ assert(*iaddr == 0);
+
+ *iinst = inst | IPC_FLAG;
+ *iaddr = addr;
+}
+
+void core_step(struct Core *core) {
+ assert(core);
+
+ if (core->psli != 0) {
+ core_pull_ipcm(core);
+
+ // Save execution event locations in database
+ assert(mvec_proc_is_live(core, core->pcur));
+
+ uint64_t pcur_ip = arch_proc_ip_addr(core, core->pcur);
+
+ if (mvec_is_in_mb0_of_proc(core, pcur_ip, core->pcur)) {
+ ++core->emb0;
+ } else if (mvec_is_in_mb1_of_proc(core, pcur_ip, core->pcur)) {
+ ++core->emb1;
+ } else if (mvec_is_alloc(core, pcur_ip)) {
+ ++core->eliv;
+ } else {
+ ++core->edea;
+ }
+
+#if defined(MVEC_LOOP)
+ core->eeva[mvec_loop(pcur_ip)]++;
+#else
+ if (pcur_ip < MVEC_SIZE) {
+ core->eeva[pcur_ip]++;
+ }
+#endif
+
+ arch_proc_step(core, core->pcur);
+
+ core->psli--;
+ core->ivpt++;
+
+ return;
+ }
+
+ if (core->pcur != core->plst) {
+ core->psli = arch_proc_slice(core, ++core->pcur);
+ core_step(core);
+ return;
+ }
+
+ core->pcur = core->pfst;
+ core->psli = arch_proc_slice(core, core->pcur);
+ core->cycl++;
+
+ // TODO: Implement a day-night cycle
+ while (core->mall > MVEC_SIZE / 2 && core->pnum > 1) {
+ proc_kill(core);
+ }
+
+#if SEED != 0
+ muta_cosmic_ray(core);
+#endif
+ core_step(core);
+}
+
+// ----------------------------------------------------------------------------
+// [section] salis functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+void salis_save(const char *path) {
+ size_t size = 0;
+ char *in = NULL;
+ FILE *f = open_memstream(&in, &size);
+
+ assert(f);
+
+ for (int i = 0; i < CORES; ++i) {
+ core_save(f, &g_cores[i]);
+ }
+
+ fwrite(&g_steps, sizeof(uint64_t), 1, f);
+ fwrite(&g_syncs, sizeof(uint64_t), 1, f);
+ fclose(f);
+
+ assert(size);
+ char *out = malloc(size);
+ assert(out);
+
+ struct DeflateParams params = {
+ .size = size,
+ .in = (Bytef *)in,
+ .out = (Bytef *)out,
+ };
+
+ comp_deflate(&params);
+
+ FILE *fx = fopen(path, "wb");
+ assert(fx);
+
+ fwrite(&size, sizeof(size_t), 1, fx);
+ fwrite(out, sizeof(char), params.strm.total_out, fx);
+ fclose(fx);
+
+ comp_deflate_end(&params);
+
+ free(in);
+ free(out);
+}
+
+void salis_auto_save(void) {
+#if defined(NDEBUG)
+ snprintf(
+#else
+ int rem = snprintf(
+#endif
+ g_asav_pbuf,
+ AUTOSAVE_NAME_LEN,
+ "%s-%#018lx",
+ SIM_PATH,
+ g_steps
+ );
+
+ assert(rem >= 0);
+ assert(rem < AUTOSAVE_NAME_LEN);
+
+ log_info("Saving simulation state on step %#lx", g_steps);
+ salis_save(g_asav_pbuf);
+}
+#endif
+
+#if defined(COMMAND_NEW)
+void salis_push_data_header(void) {
+ assert(g_sim_db);
+
+ log_info("Creating core table in SQLite database");
+ sql_exec(
+ NULL, NULL,
+ "create table core ("
+#define EVENT_ARRAY(core, index, ev) \
+ #ev EVENT_ARRAYS_SIZE_COL_MARKER #core " int not null, " \
+ #ev "_" #core " blob, "
+#define FOR_CORE(i) \
+ "cycl_" #i " int not null, " \
+ "mall_" #i " int not null, " \
+ "pnum_" #i " int not null, " \
+ "pfst_" #i " int not null, " \
+ "plst_" #i " int not null, " \
+ "amb0_" #i " real not null, " \
+ "amb1_" #i " real not null, " \
+ "emb0_" #i " int not null, " \
+ "emb1_" #i " int not null, " \
+ "eliv_" #i " int not null, " \
+ "edea_" #i " int not null, " \
+ EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef EVENT_ARRAY
+ "step int not null"
+ ");"
+ );
+
+ arch_push_data_header();
+}
+#endif
+
+void salis_push_data_line(void) {
+ assert(g_sim_db);
+
+ // Measure average membory block sizes
+ double amb0[CORES] = { 0 };
+ double amb1[CORES] = { 0 };
+
+ for (int i = 0; i < CORES; ++i) {
+ struct Core *core = &g_cores[i];
+
+ for (uint64_t j = core->pfst; j <= core->plst; ++j) {
+ amb0[i] += (double)arch_proc_mb0_size(core, j);
+ amb1[i] += (double)arch_proc_mb1_size(core, j);
+ }
+
+ amb0[i] /= core->pnum;
+ amb1[i] /= core->pnum;
+ }
+
+ // Compress event arrays
+ // Compression (deflation) is CPU intensive so it's done in parallel
+ memset(&g_eva_deflate_params, 0, sizeof(struct DeflateParams) * CORES * EVENT_ARRAYS_COUNT);
+ uint64_t blob_sizes[CORES][EVENT_ARRAYS_COUNT];
+
+ for (int i = 0; i < CORES; ++i) {
+ for (int j = 0; j < EVENT_ARRAYS_COUNT; ++j) {
+ uint64_t *in = NULL;
+
+ switch (j) {
+#define EVENT_ARRAY(core, index, ev) \
+ case index: in = g_cores[i].ev##a; break;
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+ default: assert(false);
+ }
+
+ // Compress event data
+ struct DeflateParams *params = &g_eva_deflate_params[i][j];
+ params->size = EVENT_ARRAYS_SIZE,
+ params->in = (Bytef *)in,
+ params->out = (Bytef *)malloc(EVENT_ARRAYS_SIZE),
+ thrd_create(&g_eva_thrds[i][j], (thrd_start_t)comp_deflate, params);
+ }
+ }
+
+ int rem = snprintf(
+ g_eva_pbuf,
+ EVA_SAVE_NAME_LEN,
+ "%s/evas-%#018lx",
+ SIM_EDIR,
+ g_steps
+ );
+
+ assert(rem >= 0);
+ assert(rem < EVA_SAVE_NAME_LEN);
+ (void)rem;
+
+ log_info("Saving event-array data to file: %s", g_eva_pbuf);
+ FILE *eva_file = fopen(g_eva_pbuf, "wb");
+
+ for (int i = 0; i < CORES; ++i) {
+ for (int j = 0; j < EVENT_ARRAYS_COUNT; ++j) {
+ thrd_join(g_eva_thrds[i][j], NULL);
+ struct DeflateParams *params = &g_eva_deflate_params[i][j];
+ blob_sizes[i][j] = params->strm.total_out;
+ fwrite(params->out, sizeof(char), blob_sizes[i][j], eva_file);
+ comp_deflate_end(params);
+ free(params->out);
+ }
+ }
+
+ log_info("Pushing row to core table in SQLite database");
+ sql_exec(
+ NULL, NULL,
+ "insert into core ("
+#define EVENT_ARRAY(core, index, ev) \
+ #ev EVENT_ARRAYS_SIZE_COL_MARKER #core ", "
+#define FOR_CORE(i) \
+ "cycl_" #i ", " \
+ "mall_" #i ", " \
+ "pnum_" #i ", " \
+ "pfst_" #i ", " \
+ "plst_" #i ", " \
+ "amb0_" #i ", " \
+ "amb1_" #i ", " \
+ "emb0_" #i ", " \
+ "emb1_" #i ", " \
+ "eliv_" #i ", " \
+ "edea_" #i ", " \
+ EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef EVENT_ARRAY
+ "step"
+ ") values ("
+#define EVENT_ARRAY(core, index, ev) \
+ "%ld, "
+#define FOR_CORE(i) \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%f, " \
+ "%f, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef EVENT_ARRAY
+ "%ld"
+ ");",
+#define EVENT_ARRAY(core, index, ev) \
+ blob_sizes[core][index],
+#define FOR_CORE(i) \
+ g_cores[i].cycl, \
+ g_cores[i].mall, \
+ g_cores[i].pnum, \
+ g_cores[i].pfst, \
+ g_cores[i].plst, \
+ amb0[i], \
+ amb1[i], \
+ g_cores[i].emb0, \
+ g_cores[i].emb1, \
+ g_cores[i].eliv, \
+ g_cores[i].edea, \
+ EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef EVENT_ARRAY
+ g_steps
+ );
+
+ // Reset data aggregation fields
+ for (int i = 0; i < CORES; ++i) {
+ struct Core *core = &g_cores[i];
+
+ core->emb0 = 0;
+ core->emb1 = 0;
+ core->eliv = 0;
+ core->edea = 0;
+
+#define EVENT_ARRAY(core, index, ev) \
+ memset(core->ev##a, 0, EVENT_ARRAYS_SIZE);
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+ }
+
+ // Push arch-specific data
+ arch_push_data_line(eva_file);
+ fclose(eva_file);
+}
+
+#if defined(COMMAND_NEW)
+void salis_init(void) {
+ uint64_t seed = SEED;
+
+ for (int i = 0; i < CORES; ++i) {
+ core_init(&g_cores[i], &seed);
+ }
+
+#if defined(COMMAND_NEW)
+ salis_auto_save();
+#endif
+
+ // Initialize database
+ sql_open();
+ salis_push_data_header();
+ salis_push_data_line();
+}
+#endif
+
+#if defined(COMMAND_LOAD)
+void salis_load(void) {
+ FILE *fx = fopen(SIM_PATH, "rb");
+ assert(fx);
+
+ fseek(fx, 0, SEEK_END);
+ size_t x_size = ftell(fx) - sizeof(size_t);
+ char *in = malloc(x_size);
+ rewind(fx);
+ assert(x_size);
+ assert(in);
+
+ size_t size = 0;
+ fread(&size, sizeof(size_t), 1, fx);
+ fread(in, 1, x_size, fx);
+ fclose(fx);
+ assert(size);
+
+ char *out = malloc(size);
+ assert(out);
+
+ struct InflateParams params = {
+ .avail_in = x_size,
+ .size = size,
+ .in = (Bytef *)in,
+ .out = (Bytef *)out,
+ };
+
+ comp_inflate(&params);
+ comp_inflate_end(&params);
+
+ FILE *f = fmemopen(out, size, "rb");
+
+ assert(f);
+
+ for (int i = 0; i < CORES; ++i) {
+ core_load(f, &g_cores[i]);
+ }
+
+ fread(&g_steps, sizeof(uint64_t), 1, f);
+ fread(&g_syncs, sizeof(uint64_t), 1, f);
+ fclose(f);
+ free(in);
+ free(out);
+
+ sql_open();
+}
+#endif
+
+int salis_thread(struct Core *core) {
+ assert(core);
+
+ for (uint64_t i = 0; i < core->thrd_steps; ++i) {
+ core_step(core);
+ }
+
+ return 0;
+}
+
+void salis_run_thread(uint64_t ns) {
+ for (int i = 0; i < CORES; ++i) {
+ g_cores[i].thrd_steps = ns;
+
+ thrd_create(
+ &g_cores[i].thrd,
+ (thrd_start_t)salis_thread,
+ &g_cores[i]
+ );
+ }
+
+ for (int i = 0; i < CORES; ++i) {
+ thrd_join(g_cores[i].thrd, NULL);
+ }
+
+ g_steps += ns;
+}
+
+void salis_sync(void) {
+#if !defined(NDEBUG)
+ for (int i = 0; i < CORES; ++i) {
+ assert(g_cores[i].ivpt == SYNC_INTERVAL);
+ }
+#endif
+
+ uint8_t *iviv0 = g_cores[0].iviv;
+ uint64_t *ivav0 = g_cores[0].ivav;
+
+ for (int i = 1; i < CORES; ++i) {
+ g_cores[i - 1].iviv = g_cores[i].iviv;
+ g_cores[i - 1].ivav = g_cores[i].ivav;
+ }
+
+ g_cores[CORES - 1].iviv = iviv0;
+ g_cores[CORES - 1].ivav = ivav0;
+
+ for (int i = 0; i < CORES; ++i) {
+ g_cores[i].ivpt = 0;
+ }
+
+ g_syncs++;
+}
+
+void salis_loop(uint64_t ns, uint64_t dt) {
+ assert(dt);
+
+ if (ns < dt) {
+ salis_run_thread(ns);
+ return;
+ }
+
+ salis_run_thread(dt);
+ salis_sync();
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+ if (g_steps % AUTOSAVE_INTERVAL == 0) {
+ salis_auto_save();
+ }
+#endif
+
+ if (g_steps % DATA_PUSH_INTERVAL == 0) {
+ salis_push_data_line();
+ }
+
+ salis_loop(ns - dt, SYNC_INTERVAL);
+}
+
+#if !defined(NDEBUG)
+void salis_validate_core(const struct Core *core) {
+ assert(core->cycl <= g_steps);
+ assert(core->plst >= core->pfst);
+ assert(core->pnum == core->plst + 1 - core->pfst);
+ assert(core->pnum <= core->pcap);
+ assert(core->pcur >= core->pfst && core->pcur <= core->plst);
+
+ uint64_t mall = 0;
+
+ for (uint64_t i = 0; i < MVEC_SIZE; ++i) {
+ mall += mvec_is_alloc(core, i) ? 1 : 0;
+ }
+
+ assert(core->mall == mall);
+
+ for (uint64_t i = core->pfst; i <= core->plst; ++i) {
+ arch_validate_proc(core, i);
+ }
+
+ for (uint64_t i = 0; i < SYNC_INTERVAL; ++i) {
+ uint8_t iinst = core->iviv[i];
+
+ if ((iinst & IPC_FLAG) == 0) {
+ uint64_t iaddr = core->ivav[i];
+
+ assert(iinst == 0);
+ assert(iaddr == 0);
+ }
+ }
+
+ assert(core->ivpt == g_steps % SYNC_INTERVAL);
+}
+
+void salis_validate(void) {
+ assert(g_steps / SYNC_INTERVAL == g_syncs);
+
+ for (int i = 0; i < CORES; ++i) {
+ salis_validate_core(&g_cores[i]);
+ }
+}
+#endif
+
+void salis_step(uint64_t ns) {
+ assert(ns);
+ salis_loop(ns, SYNC_INTERVAL - (g_steps % SYNC_INTERVAL));
+
+#if !defined(NDEBUG)
+ salis_validate();
+#endif
+}
+
+void salis_free(void) {
+ sql_close();
+
+ for (int i = 0; i < CORES; ++i) {
+ arch_core_free(&g_cores[i]);
+
+ assert(g_cores[i].pvec);
+ assert(g_cores[i].iviv);
+ assert(g_cores[i].ivav);
+
+ free(g_cores[i].pvec);
+ free(g_cores[i].iviv);
+ free(g_cores[i].ivav);
+
+ g_cores[i].pvec = NULL;
+ g_cores[i].iviv = NULL;
+ g_cores[i].ivav = NULL;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] architecture & ui includes
+// ----------------------------------------------------------------------------
+#include "arch.c"
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+#include "ui.c"
+#endif
diff --git a/core/server.c b/core/server.c
new file mode 100644
index 0000000..6848cf6
--- /dev/null
+++ b/core/server.c
@@ -0,0 +1,418 @@
+// index
+// [section] includes
+// [section] macros
+// [section] structs
+// [section] globals
+// [section] event array render function
+// [section] sql callbacks
+// [section] main functions
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <arpa/inet.h>
+#include <assert.h>
+#include <json-c/json.h>
+#include <signal.h>
+#include <sqlite3.h>
+#include <string.h>
+#include <threads.h>
+#include <zlib.h>
+
+#include "common.c"
+#include "compress.c"
+#include "logger.c"
+#include "sql.c"
+
+// ----------------------------------------------------------------------------
+// [section] macros
+// ----------------------------------------------------------------------------
+#define BACKLOG 10
+
+// ----------------------------------------------------------------------------
+// [section] structs
+// ----------------------------------------------------------------------------
+struct Socket {
+ int fd;
+ struct sockaddr_in addr;
+};
+
+struct CallbackContext {
+ struct json_object *response;
+ int64_t response_rows;
+ int64_t hm_left;
+ int64_t hm_pixel_count;
+ int64_t hm_pixel_pow;
+};
+
+struct RenderContext {
+ const struct CallbackContext *callback_context;
+ void *blob;
+ size_t blob_size;
+ uint64_t eva[EVENT_ARRAYS_SIZE];
+ int64_t out[DEFVAL_HM_PIXEL_COUNT];
+};
+
+// ----------------------------------------------------------------------------
+// [section] globals
+// ----------------------------------------------------------------------------
+struct json_object *g_response_header;
+size_t g_eva_count;
+char g_eva_pbuf[EVA_SAVE_NAME_LEN];
+
+// ----------------------------------------------------------------------------
+// [section] event array render function
+// ----------------------------------------------------------------------------
+int eva_render(void *data) {
+ assert(data);
+
+ struct RenderContext *render_context = (struct RenderContext *)data;
+ int64_t hm_left = render_context->callback_context->hm_left;
+ int64_t hm_pixel_count = render_context->callback_context->hm_pixel_count;
+ int64_t hm_pixel_pow = render_context->callback_context->hm_pixel_pow;
+ int64_t hm_pixel_res = 1 << hm_pixel_pow;
+ const void *blob = render_context->blob;
+ size_t blob_size = render_context->blob_size;
+
+ assert(blob);
+
+#if defined(MVEC_LOOP)
+ hm_left %= MVEC_SIZE;
+#endif
+
+#if !defined(MVEC_LOOP)
+#if !defined(NDEBUG)
+ int64_t hm_right = hm_left + hm_pixel_res * hm_pixel_count;
+#endif
+ assert(hm_left < (int64_t)MVEC_SIZE);
+ assert(hm_right <= (int64_t)MVEC_SIZE);
+#endif
+
+ // Inflate blob
+ struct InflateParams params = {
+ .avail_in = blob_size,
+ .size = EVENT_ARRAYS_SIZE,
+ .in = (Bytef *)blob,
+ .out = (Bytef *)render_context->eva,
+ };
+
+ comp_inflate(&params);
+ comp_inflate_end(&params);
+
+ for (int64_t i = 0; i < hm_pixel_count; i++) {
+ render_context->out[i] = 0l;
+
+ for (int64_t j = 0; j < hm_pixel_res; j++) {
+ int64_t coord = hm_left + (i * hm_pixel_res) + j;
+#if defined(MVEC_LOOP)
+ coord %= MVEC_SIZE;
+#endif
+ render_context->out[i] += render_context->eva[coord];
+ }
+ }
+
+ return 0;
+}
+
+// ----------------------------------------------------------------------------
+// [section] sql callbacks
+// ----------------------------------------------------------------------------
+void sql_callback_add_column_name(sqlite3_stmt *sql_stmt, void *data) {
+ assert(sql_stmt);
+ assert(data);
+ assert(sqlite3_column_type(sql_stmt, 0) == SQLITE_TEXT);
+ assert(sqlite3_column_type(sql_stmt, 1) == SQLITE_TEXT);
+ assert(!strcmp(sqlite3_column_name(sql_stmt, 0), "name"));
+ assert(!strcmp(sqlite3_column_name(sql_stmt, 1), "type"));
+
+ const char *col_name = (const char *)sqlite3_column_text(sql_stmt, 0);
+ struct json_object *response_header = (struct json_object *)data;
+
+ if (!json_object_object_get_ex(response_header, col_name, NULL)) {
+ json_object_object_add(response_header, col_name, json_object_new_array());
+ }
+
+ if (strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER)) {
+ g_eva_count++;
+ }
+}
+
+void sql_callback_add_data(sqlite3_stmt *sql_stmt, void *data) {
+ assert(sql_stmt);
+ assert(data);
+
+ FILE *eva_file = NULL;
+ struct CallbackContext *callback_context = (struct CallbackContext *)data;
+ struct RenderContext *render_contexts = calloc(g_eva_count, sizeof(struct RenderContext));
+ thrd_t *threads = calloc(g_eva_count, sizeof(thrd_t));
+ size_t tid = 0;
+
+ for (int i = 0; i < sqlite3_column_count(sql_stmt); i++) {
+ assert(i < sqlite3_column_count(sql_stmt));
+ assert(sqlite3_column_type(sql_stmt, i) == SQLITE_INTEGER || sqlite3_column_type(sql_stmt, i) == SQLITE_FLOAT);
+
+ const char *col_name = sqlite3_column_name(sql_stmt, i);
+ struct json_object *col_data = json_object_object_get(callback_context->response, col_name);
+ assert(col_name);
+
+ if (!col_data) continue;
+
+ int64_t col_value = sqlite3_column_int64(sql_stmt, i);
+ json_object_array_add(col_data, json_object_new_int64(col_value));
+
+ if (i == 1) {
+ assert(!strcmp(col_name, "step"));
+
+ int rem = snprintf(
+ g_eva_pbuf,
+ EVA_SAVE_NAME_LEN,
+ "%s/evas-%#018lx",
+ SIM_EDIR,
+ col_value
+ );
+
+ assert(rem >= 0);
+ assert(rem < EVA_SAVE_NAME_LEN);
+ (void)rem;
+
+ eva_file = fopen(g_eva_pbuf, "rb");
+ assert(eva_file);
+ }
+
+ if (sqlite3_column_type(sql_stmt, i + 1) == SQLITE_NULL) {
+ assert(eva_file);
+ assert(strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER));
+
+ render_contexts[tid].callback_context = callback_context;
+ render_contexts[tid].blob = malloc(col_value);
+ render_contexts[tid].blob_size = col_value;
+ assert(render_contexts[tid].blob);
+
+ size_t red = fread(render_contexts[tid].blob, 1, col_value, eva_file);
+ assert(red == (size_t)col_value);
+ (void)red;
+
+ thrd_create(&threads[tid], (thrd_start_t)eva_render, &render_contexts[tid]);
+
+ tid++;
+ i++;
+ }
+ }
+
+ assert(tid == g_eva_count);
+ tid = 0;
+
+ for (int i = 0; i < sqlite3_column_count(sql_stmt); i++) {
+ const char *col_name = sqlite3_column_name(sql_stmt, i);
+ struct json_object *col_data = json_object_object_get(callback_context->response, col_name);
+ assert(col_name);
+
+ if (!col_data) continue;
+
+ if (sqlite3_column_type(sql_stmt, i + 1) == SQLITE_NULL) {
+ assert(strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER));
+ assert(render_contexts[tid].blob);
+
+ const char *eva_col_name = sqlite3_column_name(sql_stmt, i + 1);
+ struct json_object *eva_col_data = json_object_object_get(callback_context->response, eva_col_name);
+ assert(eva_col_name);
+ assert(eva_col_data);
+
+ thrd_join(threads[tid], NULL);
+
+ for (int64_t j = 0; j < callback_context->hm_pixel_count; j++) {
+ json_object_array_add(eva_col_data, json_object_new_int64(render_contexts[tid].out[j]));
+ }
+
+ free(render_contexts[tid].blob);
+
+ tid++;
+ i++;
+ }
+ }
+
+ assert(tid == g_eva_count);
+
+ callback_context->response_rows++;
+ log_info("Processed row #%ld", callback_context->response_rows);
+
+ assert(eva_file);
+ fclose(eva_file);
+ free(render_contexts);
+ free(threads);
+}
+
+// ----------------------------------------------------------------------------
+// [section] main functions
+// ----------------------------------------------------------------------------
+void sig_handler(int signo) {
+ (void)signo;
+
+ log_warn("Signal received, will stop SALIS data server");
+ json_object_put(g_response_header);
+ sql_close();
+ exit(0);
+}
+
+void respond_name(int socket_fd) {
+ log_info("Client requested simulation name");
+
+ struct json_object *sim_name = json_object_new_object();
+ json_object_object_add(sim_name, "name", json_object_new_string(NAME));
+ json_object_to_fd(socket_fd, sim_name, 0);
+ json_object_put(sim_name);
+}
+
+void respond_opts(int socket_fd) {
+ log_info("Client requested simulation options");
+
+ struct json_object *sim_opts = json_object_from_file(SIM_OPTS);
+ json_object_to_fd(socket_fd, sim_opts, 0);
+ json_object_put(sim_opts);
+}
+
+void respond_hash(int socket_fd) {
+ log_info("Client requested git hash");
+
+ char buff[41] = { 0 };
+ FILE *pipe = popen("git rev-parse HEAD", "r");
+ fread(buff, sizeof(char), 40, pipe);
+ pclose(pipe);
+
+ struct json_object *git_hash = json_object_new_object();
+ json_object_object_add(git_hash, "hash", json_object_new_string(buff));
+ json_object_to_fd(socket_fd, git_hash, 0);
+ json_object_put(git_hash);
+}
+
+void respond_data(int socket_fd, struct json_object *request) {
+ assert(request);
+
+ const char *request_str = json_object_to_json_string(request);
+ log_info("Client requested simulation data with the following parameters: %s", request_str);
+ const char *x_axis = json_object_get_string(json_object_object_get(request, "x-axis"));
+ int64_t x_current = json_object_get_int64(json_object_object_get(request, "x-current"));
+ int64_t x_high = json_object_get_int64(json_object_object_get(request, "x-high"));
+ int64_t nth = json_object_get_int64(json_object_object_get(request, "nth"));
+ int64_t entries = json_object_get_int64(json_object_object_get(request, "entries"));
+
+ struct CallbackContext callback_context = {
+ .response = NULL,
+ .response_rows = 0l,
+ .hm_left = json_object_get_int64(json_object_object_get(request, "hm-left")),
+ .hm_pixel_count = json_object_get_int64(json_object_object_get(request, "hm-pixel-count")),
+ .hm_pixel_pow = json_object_get_int64(json_object_object_get(request, "hm-pixel-pow")),
+ };
+
+ json_object_deep_copy(g_response_header, &callback_context.response, NULL);
+
+ const char *x_axis_pref = (!strcmp(x_axis, "rowid") || !strcmp(x_axis, "step")) ? "core." : "";
+
+ sql_exec(
+ sql_callback_add_data,
+ &callback_context,
+ "select * from ("
+ "select core.rowid, core.step, * from core inner join arch "
+ "where core.rowid = arch.rowid and %s%s > %ld and %s%s <= %ld and core.rowid %% %ld == 0 "
+ "order by %s%s desc limit %ld"
+ ") order by %s asc;",
+ x_axis_pref,
+ x_axis,
+ x_current,
+ x_axis_pref,
+ x_axis,
+ x_high,
+ nth,
+ x_axis_pref,
+ x_axis,
+ entries,
+ x_axis
+ );
+
+ log_info("Sending client %ld rows of data", callback_context.response_rows);
+ json_object_to_fd(socket_fd, callback_context.response, 0);
+ json_object_put(callback_context.response);
+
+ shutdown(socket_fd, SHUT_WR);
+}
+
+int handle_client(struct Socket *socket) {
+ assert(socket);
+
+ char socket_ip[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, &socket->addr.sin_addr, socket_ip, INET_ADDRSTRLEN);
+ log_info("Client connected: %s:%d", socket_ip, ntohs(socket->addr.sin_port));
+
+ struct json_object *request_json = json_object_from_fd(socket->fd);
+ struct json_object *request_str = NULL;
+
+ if (!json_object_object_get_ex(request_json, "request", &request_str)) assert(false);
+
+ const char *request = json_object_get_string(request_str);
+ assert(request);
+
+ if (!strcmp(request, "name")) {
+ respond_name(socket->fd);
+ } else if (!strcmp(request, "opts")) {
+ respond_opts(socket->fd);
+ } else if (!strcmp(request, "hash")) {
+ respond_hash(socket->fd);
+ } else if (!strcmp(request, "data")) {
+ respond_data(socket->fd, request_json);
+ } else {
+ assert(false);
+ }
+
+ json_object_put(request_json);
+
+ log_info("Client disconnected: %s:%d", socket_ip, ntohs(socket->addr.sin_port));
+ close(socket->fd);
+
+ free(socket);
+ return 0;
+}
+
+int main(void) {
+ log_info("Initializing salis data server");
+ log_info("Connecting to database in: %s", DATA_PUSH_PATH);
+ sql_open();
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+ signal(SIGPIPE, SIG_IGN); // ignore broken pipes
+
+ log_info("Creating response header");
+ g_response_header = json_object_new_object();
+ json_object_object_add(g_response_header, "rowid", json_object_new_array());
+ sql_exec(
+ sql_callback_add_column_name,
+ g_response_header,
+ "select name, type from pragma_table_info('core') union select name, type from pragma_table_info('arch');"
+ );
+ log_info("Found %lu eva-size columns in database", g_eva_count);
+
+ log_info("Binding to port: %d", PORT);
+ int opt = 1;
+ int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+ setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ struct sockaddr_in socket_addr = { 0 };
+ socket_addr.sin_family = AF_INET;
+ socket_addr.sin_addr.s_addr = INADDR_ANY;
+ socket_addr.sin_port = htons(PORT);
+ bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(struct sockaddr_in));
+
+ listen(socket_fd, BACKLOG);
+ log_info("Listening...");
+
+ while (true) {
+ struct Socket *socket = calloc(1, sizeof(struct Socket));
+ socklen_t socket_len = sizeof(struct sockaddr_in);
+ socket->fd = accept(socket_fd, (struct sockaddr *)&socket->addr, &socket_len);
+
+ thrd_t thread;
+ thrd_create(&thread, (thrd_start_t)handle_client, socket);
+ thrd_detach(thread);
+ }
+
+ return 0;
+}
diff --git a/core/sql.c b/core/sql.c
new file mode 100644
index 0000000..760adc4
--- /dev/null
+++ b/core/sql.c
@@ -0,0 +1,73 @@
+#define DATA_PUSH_BUSY_TIMEOUT 600000
+
+sqlite3 *g_sim_db;
+
+void sql_exec(void (*callback)(sqlite3_stmt *sql_stmt, void *data), void *data, const char *sql_format, ...) {
+ assert(sql_format);
+
+ va_list args;
+ va_start(args, sql_format);
+ int sql_len = vsnprintf(NULL, 0, sql_format, args) + 1;
+ char *sql_str = malloc(sql_len);
+ assert(sql_str);
+ va_end(args);
+
+ va_start(args, sql_format);
+ vsprintf(sql_str, sql_format, args);
+ va_end(args);
+
+ int sql_res;
+ sqlite3_stmt *sql_stmt;
+
+ sql_res = sqlite3_prepare_v2(g_sim_db, sql_str, -1, &sql_stmt, NULL);
+ assert(sql_res == SQLITE_OK);
+ free(sql_str);
+
+ while (true) {
+ sql_res = sqlite3_step(sql_stmt);
+
+ if (sql_res == SQLITE_ROW) {
+ if (callback) {
+ callback(sql_stmt, data);
+ }
+
+ continue;
+ }
+
+ if (sql_res == SQLITE_DONE) {
+ break;
+ }
+
+ log_warn("SQLite database returned error %d with message:", sql_res);
+ log_warn(sqlite3_errmsg(g_sim_db));
+
+ // Only handle SQLITE_BUSY error, in which case we retry the query.
+ // Setting 'journal_mode=wal;' should help prevent busy database errors.
+ if (sql_res == SQLITE_BUSY) {
+ log_info("Will retry query...");
+ continue;
+ }
+
+ assert(false);
+ }
+
+ sqlite3_finalize(sql_stmt);
+}
+
+void sql_open(void) {
+ sqlite3_open(DATA_PUSH_PATH, &g_sim_db);
+ assert(g_sim_db);
+
+ // Install busy handler to retry transactions if DB is locked
+ sqlite3_busy_timeout(g_sim_db, DATA_PUSH_BUSY_TIMEOUT);
+
+ // Enable Write-Ahead Logging (WAL)
+ // This seems to help prevent DB locks when displaying live data.
+ // See: https://sqlite.org/wal.html
+ sql_exec(NULL, NULL, "pragma journal_mode=wal;");
+}
+
+void sql_close(void) {
+ assert(g_sim_db);
+ sqlite3_close(g_sim_db);
+}