Skip to content

Render summary benchmark results as Markdown tables#621

Open
reflecttypefor wants to merge 1 commit into
mratsim:masterfrom
reflecttypefor:master
Open

Render summary benchmark results as Markdown tables#621
reflecttypefor wants to merge 1 commit into
mratsim:masterfrom
reflecttypefor:master

Conversation

@reflecttypefor

@reflecttypefor reflecttypefor commented Jun 18, 2026

Copy link
Copy Markdown

Summary

This adds a small Markdown table reporting helper for benchmark output and uses it in the summary benchmark template.

The summary benchmark output can now be copied directly into GitHub, ethresear.ch, and ethereum-magicians.org without losing alignment due to line wrapping.

This is a first focused step toward #522, keeping the change limited to the summary benchmarks before expanding the same helper to other benchmark suites.

Changes

  • Add benchmarks/bench_report.nim with Markdown table formatting helpers
  • Escape Markdown table cells containing pipes or newlines
  • Render bench_summary_template benchmark rows as Markdown tables
  • Add tests for Markdown header/separator/row formatting
  • Register the new test in constantine.nimble

Summary by CodeRabbit

  • New Features

    • Added Markdown table formatting for benchmark reports with automatic cell escaping and proper table structure generation.
  • Tests

    • Added test suite for report formatting functionality to verify Markdown table rendering and cell escaping.
  • Chores

    • Updated build configuration to include new test suite.

Signed-off-by: reflecttypefor <reflecttypefor@outlook.com>
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds benchmarks/bench_report.nim with Markdown escape and table-construction helpers (markdownEscape, markdownTableHeader, markdownTableSeparator, markdownTableRow, reportMarkdownTable). Updates bench_summary_template.nim to call reportMarkdownTable instead of raw echo. Adds a unittest suite in tests/t_bench_report.nim and registers it in constantine.nimble.

Changes

Benchmark Markdown Report Helpers

Layer / File(s) Summary
bench_report.nim: escape, table helpers, and reportMarkdownTable
benchmarks/bench_report.nim
Defines markdownEscape to sanitize cell content (escaping \ and |, stripping \r, converting \n to <br>), table-building procs for header, separator, and row strings, and reportMarkdownTable which asserts equal header/cell lengths and uses a printedMarkdownHeaders cache to emit the header+separator only once per unique column set.
bench_summary_template.nim: adopt reportMarkdownTable
benchmarks/bench_summary_template.nim
Imports ./bench_report and replaces two echo statements in report with two reportMarkdownTable calls: one with a five-column header including CPU cycles, one with four columns omitting it.
Tests and nimble registration
tests/t_bench_report.nim, constantine.nimble
Adds a Benchmark Markdown reports unittest suite checking header/separator pipe formatting and |/newline escaping in cells; registers tests/t_bench_report.nim in constantine.nimble's testDesc list.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A table in Markdown, so neat and so clean,
With pipes and with headers to fill up the screen.
Escaped are the backticks, the newlines replaced,
And headers repeat not — efficiently spaced.
Hop hop, the report now looks perfectly placed! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Render summary benchmark results as Markdown tables' accurately and concisely describes the main change: adding Markdown table formatting for benchmark reporting as detailed in the PR objectives and file summaries.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Markdown table reporting for benchmarks by adding a new bench_report module, updating the benchmark summary template to output Markdown tables, and adding corresponding unit tests. The feedback highlights an issue where the global printedMarkdownHeaders variable is never cleared, which can lead to broken Markdown formatting when multiple separate tables with identical headers are printed in a single run. A helper function to reset this state is recommended.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +36 to +48
proc reportMarkdownTable*(headers, cells: openArray[string]) =
doAssert headers.len == cells.len

let key = headers.join("\t")
for printedHeader in printedMarkdownHeaders:
if printedHeader == key:
echo markdownTableRow(cells)
return

echo markdownTableHeader(headers)
echo markdownTableSeparator(headers.len)
printedMarkdownHeaders.add key
echo markdownTableRow(cells)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Because printedMarkdownHeaders is a global variable that is never cleared, any subsequent table with the same headers in the same benchmark run will omit the header and separator lines. This will result in broken Markdown rendering if there is intervening text or if they are meant to be separate tables (e.g., when benchmarking different curves or categories in a single run).

To support multiple separate tables with identical headers, we should expose a helper function to reset/clear the printed headers tracker.

proc clearPrintedHeaders*() =
  ## Clear the list of printed headers to allow starting a new table
  ## with the same headers.
  printedMarkdownHeaders.setLen(0)

proc reportMarkdownTable*(headers, cells: openArray[string]) =
  doAssert headers.len == cells.len

  let key = headers.join("\t")
  for printedHeader in printedMarkdownHeaders:
    if printedHeader == key:
      echo markdownTableRow(cells)
      return

  echo markdownTableHeader(headers)
  echo markdownTableSeparator(headers.len)
  printedMarkdownHeaders.add key
  echo markdownTableRow(cells)

@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a bench_report.nim helper module that formats benchmark output as Markdown tables and wires it into bench_summary_template.nim, replacing the previous fixed-width echo lines. The goal is to make summary benchmark results paste-ready into GitHub and forum posts without wrapping issues.

  • bench_report.nim introduces markdownEscape, markdownTableHeader/Separator/Row pure funcs and a stateful reportMarkdownTable proc that tracks which header schemas have been printed (via a module-level seq[string]) to avoid repeating header rows within a single benchmark run.
  • bench_summary_template.nim now calls reportMarkdownTable with a 5-column schema (when SupportsGetTicks) or 4-column schema (otherwise), matching the data previously in the format strings.
  • tests/t_bench_report.nim is registered in constantine.nimble and covers the pure formatting functions, but does not test the backslash-escape path or the stateful deduplication behavior of reportMarkdownTable.

Confidence Score: 4/5

The change is additive and limited to benchmark output formatting; no production cryptographic code is touched, making it safe to merge.

The core formatting logic is straightforward and the integration into bench_summary_template is a clean mechanical replacement. The main rough edges are in bench_report.nim: the module-level printedMarkdownHeaders has no reset path, doAssert is stripped by -d:danger so a header/cell length mismatch would silently corrupt output under that build flag, and the test suite misses the backslash-escaping path. None of these affect correctness of the cryptographic library itself.

benchmarks/bench_report.nim and tests/t_bench_report.nim warrant a second look — the global state design and incomplete test coverage are the areas most likely to cause surprises as the helper is extended to other benchmark suites.

Important Files Changed

Filename Overview
benchmarks/bench_report.nim New module introducing Markdown table helpers and a stateful reportMarkdownTable proc backed by a module-level global; lacks a reset API and uses doAssert which is stripped by -d:danger.
benchmarks/bench_summary_template.nim Replaces two fixed-format echo calls in report with reportMarkdownTable; change is mechanical and correct, column schemas match the former format strings.
tests/t_bench_report.nim Unit tests cover header, separator, and cell-escaping for pipes/newlines, but miss the backslash-escape path and reportMarkdownTable's stateful deduplication logic entirely.
constantine.nimble Registers t_bench_report.nim in the test suite but places it in the KDF section rather than a more semantically appropriate location.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["bench_summary_template.nim\nreport()"] -->|"SupportsGetTicks = true"| B["reportMarkdownTable\n5 columns"]
    A -->|"SupportsGetTicks = false"| C["reportMarkdownTable\n4 columns"]
    B --> D["bench_report.nim\nreportMarkdownTable()"]
    C --> D
    D --> E{"headers.join(tab)\nalready in\nprintedMarkdownHeaders?"}
    E -->|"No (first call)"| F["echo markdownTableHeader\necho markdownTableSeparator\nadd key to global seq\necho markdownTableRow"]
    E -->|"Yes (subsequent calls)"| G["echo markdownTableRow only"]
    F --> H["Markdown table output"]
    G --> H
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["bench_summary_template.nim\nreport()"] -->|"SupportsGetTicks = true"| B["reportMarkdownTable\n5 columns"]
    A -->|"SupportsGetTicks = false"| C["reportMarkdownTable\n4 columns"]
    B --> D["bench_report.nim\nreportMarkdownTable()"]
    C --> D
    D --> E{"headers.join(tab)\nalready in\nprintedMarkdownHeaders?"}
    E -->|"No (first call)"| F["echo markdownTableHeader\necho markdownTableSeparator\nadd key to global seq\necho markdownTableRow"]
    E -->|"Yes (subsequent calls)"| G["echo markdownTableRow only"]
    F --> H["Markdown table output"]
    G --> H
Loading

Reviews (1): Last reviewed commit: "Render summary benchmark results as Mark..." | Re-trigger Greptile

Comment thread tests/t_bench_report.nim
Comment on lines +18 to +19
test "escapes Markdown table cells":
check markdownTableRow(["A | B", "line 1\nline 2"]) == "| A \\| B | line 1<br>line 2 |"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing escape coverage for backslashes

The test exercises pipe (|) and newline (\n) escaping but not the backslash path. markdownEscape replaces \ with \\ first in the multiReplace chain — if that path regresses (e.g., the order of replacements is changed), no existing test would catch it. Adding check markdownTableRow(["a\\b"]) == "| a\\\\b |" would cover this case.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +36 to +48
proc reportMarkdownTable*(headers, cells: openArray[string]) =
doAssert headers.len == cells.len

let key = headers.join("\t")
for printedHeader in printedMarkdownHeaders:
if printedHeader == key:
echo markdownTableRow(cells)
return

echo markdownTableHeader(headers)
echo markdownTableSeparator(headers.len)
printedMarkdownHeaders.add key
echo markdownTableRow(cells)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No API to start a fresh table; global state silently merges output across suites

printedMarkdownHeaders is a module-level var with no reset mechanism. If reportMarkdownTable is called from two logically separate benchmark sections in the same process run that happen to share identical column headers (e.g., two different curve suites both using the 5-column schema), the second group's rows will be silently appended to the first table with no header row — producing valid Nim output but broken Markdown. Exposing a resetMarkdownHeaders*() proc (or accepting a tableId discriminant) would give callers control without breaking the current single-table use case.

result.add " " & markdownEscape(cell) & " |"

proc reportMarkdownTable*(headers, cells: openArray[string]) =
doAssert headers.len == cells.len

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 doAssert is stripped by -d:danger

doAssert headers.len == cells.len is removed at compile time when the project is built with -d:danger. Benchmarks are often compiled with -d:danger for accurate timings. When the check is absent and the arrays are mismatched, markdownTableRow silently produces a short row, corrupting the table without any diagnostic. A runtime check that raises a ValueError would be more robust and survive all build modes.

Comment thread constantine.nimble
# KDF
# ----------------------------------------------------------
("tests/t_kdf_hkdf.nim", false),
("tests/t_bench_report.nim", false),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test registered in the wrong section

t_bench_report.nim is placed immediately after t_kdf_hkdf.nim inside the # KDF block. It would be clearer in a dedicated # Benchmarks or # Utilities section — or appended after the current last entry — so that future readers don't associate it with key-derivation tests.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
benchmarks/bench_report.nim (2)

39-39: 💤 Low value

Cache key could collide if headers contain tab characters.

The cache uses headers.join("\t") as the key. If a header cell contains a literal tab character, different header arrays could produce the same key (e.g., ["A\tB"] vs ["A", "B"]).

🛡️ Alternative: Use a delimiter that's already escaped

Since markdownEscape converts newlines to <br>, you could use \n as the delimiter:

- let key = headers.join("\t")
+ let key = headers.join("\n")

Or apply escaping before joining to guarantee uniqueness:

- let key = headers.join("\t")
+ let key = headers.mapIt(markdownEscape(it)).join("\t")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/bench_report.nim` at line 39, The cache key generation using
headers.join("\t") is vulnerable to collisions when header cells contain literal
tab characters, as different header arrays like ["A\tB"] and ["A", "B"] would
produce identical keys. Replace the tab character delimiter with a character
that is guaranteed not to appear in the header values, such as newline character
"\n" since newlines are already being converted to "<br>" by markdownEscape.
Alternatively, apply escaping to the headers before joining them to ensure the
delimiter cannot be confused with actual header content.

40-43: ⚡ Quick win

Linear search could impact performance with many distinct table formats.

The cache lookup performs a linear search through all previously seen headers. If benchmarks generate many different table formats, this O(n) lookup could become a bottleneck.

⚡ Use HashSet for O(1) lookup
-var printedMarkdownHeaders: seq[string]
+import std/sets
+
+var printedMarkdownHeaders: HashSet[string]

 proc reportMarkdownTable*(headers, cells: openArray[string]) =
   doAssert headers.len == cells.len

   let key = headers.join("\t")
-  for printedHeader in printedMarkdownHeaders:
-    if printedHeader == key:
-      echo markdownTableRow(cells)
-      return
+  if key in printedMarkdownHeaders:
+    echo markdownTableRow(cells)
+    return

   echo markdownTableHeader(headers)
   echo markdownTableSeparator(headers.len)
-  printedMarkdownHeaders.add key
+  printedMarkdownHeaders.incl key
   echo markdownTableRow(cells)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/bench_report.nim` around lines 40 - 43, Replace the linear search
through printedMarkdownHeaders with a HashSet data structure. Convert
printedMarkdownHeaders from a sequence/list to a HashSet that stores the header
keys that have already been printed. Instead of the for loop that iterates
through all elements checking equality with key, use a direct membership check
(contains operation) on the HashSet to achieve O(1) lookup time. Update both the
condition check and the insertion point where new headers are added to the set.
tests/t_bench_report.nim (1)

13-19: ⚡ Quick win

Consider expanding test coverage for edge cases and caching behavior.

The current tests verify basic formatting but miss several important scenarios:

  1. Backslash escaping: Test that \ becomes \\
  2. Carriage return removal: Test that \r is removed
  3. Caching behavior: Call reportMarkdownTable twice with the same headers to verify the header is only printed once
  4. Length mismatch: Verify doAssert triggers when headers.len != cells.len
  5. Empty cells: Test behavior with empty strings
📋 Suggested additional tests
test "escapes backslashes and handles carriage returns":
  check markdownEscape("a\\b\rc") == "a\\\\bc"

test "caching behavior prints header only once":
  # This would require capturing stdout or refactoring to return strings
  # Alternatively, test the cache directly if exposed
  discard

test "empty cells are handled":
  check markdownTableRow(["", "data"]) == "|  | data |"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/t_bench_report.nim` around lines 13 - 19, Expand the test suite to
cover additional edge cases for the markdown report functions. Add test cases
for: backslash escaping in the markdownEscape function to verify that
backslashes are properly doubled; carriage return handling to confirm that \r
characters are removed; empty cells behavior in markdownTableRow to ensure empty
strings are handled correctly; and a length mismatch test to verify that the
doAssert in reportMarkdownTable or related functions properly triggers when
headers.len does not equal cells.len. Additionally, consider adding a test for
the caching behavior of reportMarkdownTable by calling it twice with the same
headers and verifying the header is only printed once, though this may require
capturing stdout or exposing the cache for direct testing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@benchmarks/bench_report.nim`:
- Line 11: The module-level variable `printedMarkdownHeaders` is not thread-safe
and will grow unboundedly during long-running processes, potentially causing
issues in concurrent benchmarking scenarios. To fix this, apply the
`{.threadvar.}` pragma annotation to `printedMarkdownHeaders` to make it
thread-local storage, ensuring each thread maintains its own separate cache
instance and eliminating both the thread-safety concern and unbounded growth
issue.

---

Nitpick comments:
In `@benchmarks/bench_report.nim`:
- Line 39: The cache key generation using headers.join("\t") is vulnerable to
collisions when header cells contain literal tab characters, as different header
arrays like ["A\tB"] and ["A", "B"] would produce identical keys. Replace the
tab character delimiter with a character that is guaranteed not to appear in the
header values, such as newline character "\n" since newlines are already being
converted to "<br>" by markdownEscape. Alternatively, apply escaping to the
headers before joining them to ensure the delimiter cannot be confused with
actual header content.
- Around line 40-43: Replace the linear search through printedMarkdownHeaders
with a HashSet data structure. Convert printedMarkdownHeaders from a
sequence/list to a HashSet that stores the header keys that have already been
printed. Instead of the for loop that iterates through all elements checking
equality with key, use a direct membership check (contains operation) on the
HashSet to achieve O(1) lookup time. Update both the condition check and the
insertion point where new headers are added to the set.

In `@tests/t_bench_report.nim`:
- Around line 13-19: Expand the test suite to cover additional edge cases for
the markdown report functions. Add test cases for: backslash escaping in the
markdownEscape function to verify that backslashes are properly doubled;
carriage return handling to confirm that \r characters are removed; empty cells
behavior in markdownTableRow to ensure empty strings are handled correctly; and
a length mismatch test to verify that the doAssert in reportMarkdownTable or
related functions properly triggers when headers.len does not equal cells.len.
Additionally, consider adding a test for the caching behavior of
reportMarkdownTable by calling it twice with the same headers and verifying the
header is only printed once, though this may require capturing stdout or
exposing the cache for direct testing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a9ae4e34-eaad-4bfa-9fbd-e2ab33e02399

📥 Commits

Reviewing files that changed from the base of the PR and between ea8c268 and e74e1e7.

📒 Files selected for processing (4)
  • benchmarks/bench_report.nim
  • benchmarks/bench_summary_template.nim
  • constantine.nimble
  • tests/t_bench_report.nim


import std/strutils

var printedMarkdownHeaders: seq[string]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Thread-safety concern and unbounded memory growth.

The module-level printedMarkdownHeaders cache is not thread-safe and will grow unboundedly in long-running processes. If benchmarks are run concurrently or the process executes many different benchmark suites, this could cause issues.

Consider either:

  1. Using {.threadvar.} if each thread should have its own cache, or
  2. Adding explicit cache management (e.g., a clearMarkdownCache() proc), or
  3. Using a HashSet[string] with a maximum size limit
🔒 Example: Thread-local cache
-var printedMarkdownHeaders: seq[string]
+var printedMarkdownHeaders {.threadvar.}: seq[string]

Or with explicit management:

 var printedMarkdownHeaders: seq[string]
+
+proc clearMarkdownCache*() =
+  printedMarkdownHeaders.setLen(0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var printedMarkdownHeaders: seq[string]
var printedMarkdownHeaders {.threadvar.}: seq[string]
Suggested change
var printedMarkdownHeaders: seq[string]
var printedMarkdownHeaders: seq[string]
proc clearMarkdownCache*() =
printedMarkdownHeaders.setLen(0)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/bench_report.nim` at line 11, The module-level variable
`printedMarkdownHeaders` is not thread-safe and will grow unboundedly during
long-running processes, potentially causing issues in concurrent benchmarking
scenarios. To fix this, apply the `{.threadvar.}` pragma annotation to
`printedMarkdownHeaders` to make it thread-local storage, ensuring each thread
maintains its own separate cache instance and eliminating both the thread-safety
concern and unbounded growth issue.

@mratsim

mratsim commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Thank you for the PR.

I think the next step would be to modify 2 benchmark files as a branch that build on this branch and a "staggered" PR that targets this PR branch so we can see what it looks like.

I suggest:

as they are very standardized while some are somewhat customized and would need more work (https://github.com/mratsim/constantine/blob/master/benchmarks/bench_eth_evm_precompiles.nim)

You can run them with CC=clang nimble bench_fp, CC=clang nimble bench_summary_bls12_381 and in general the benchmarks are added to a nimble task in https://github.com/mratsim/constantine/blob/master/constantine.nimble#L1034-L1275

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants