aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/design/027-computation-core.md10
-rw-r--r--docs/research/140-spec-suite.md60
-rw-r--r--research/experiments/044-specs/2026-10-13-sha256.jsonl6
-rwxr-xr-xresearch/experiments/044-specs/check.py46
-rw-r--r--research/experiments/044-specs/sha256.py46
-rw-r--r--research/experiments/044-specs/time.c120
-rw-r--r--tests/check.py18
7 files changed, 306 insertions, 0 deletions
diff --git a/docs/design/027-computation-core.md b/docs/design/027-computation-core.md
index 4df67f9..f26f2d5 100644
--- a/docs/design/027-computation-core.md
+++ b/docs/design/027-computation-core.md
@@ -3,6 +3,11 @@
작성일: 2026-10-13. 상태: 설계와 단계. [026](026-app-contract.md)이 얼릴 계약 안에서 계산의 언어를 다시 세운다. 잴 도구는
[140](../research/140-spec-suite.md)의 명세 묶음이다. 1.0 전이라 호환은 챙기지 않는다. 사용자의 말: "진행해" (2026-10-13).
+1단계를 만들었다([140](../research/140-spec-suite.md)의 뒷절).
+- aup로 쓴 SHA-256이 공식 벡터를 세 실행 길에서 모두 지난다.
+- 줄 수는 막대에 못 미쳤다(aup 52 대 파이썬 28). 남은 차이는 형식 지정, 바이트 잇기와 자르기, 값으로서의 리스트다. 이것들을 2단계에 더했다.
+- 시간은 C의 176배, 순수 파이썬의 0.43배다.
+
## 쉬운 말로
- **지금의 꼴.** aup와 Pure는 패턴마다 종류와 명령을 둔다. 커서, 인덱스, 문서, 표 보기, 메시지의 행이 각각 따로다. 그래서 세상의
@@ -129,6 +134,11 @@
- 재귀하는 fn과 `while`을 둔다. Pure에는 부르기와 돌아가기, 함수마다의 검증이 생긴다.
- `for c in t`로 글자(코드 포인트)를 읽는다.
- 메시지의 필드가 아무 값을 가진다. JSON의 배열과 객체가 곧 리스트와 레코드다.
+- 1단계의 SHA-256이 길었던 까닭을 여기서 푼다.
+ - 형식 지정(`{x:08x}`, `{price:.2}`)
+ - 바이트 값을 잇고 자르기(`b + c`, `b[a .. c]`)와 바이트를 담는 `let`
+ - 값으로서의 리스트: 블록 안에서 짓고 다시 짓는다
+ - fn을 펼칠 때 리터럴 인자를 접기: 구현의 일이다. 140이 블록당 명령의 39%를 fn 호출의 몫으로 쟀다
- 먼저 이벤트와 보기의 값(메모리 안)에서 한다. 상태는 3단계다.
- **막대.**
- JSON 파서가 JSONTestSuite의 `y_`를 모두 받고 `n_`을 모두 거절한다. `i_`는 정한 쪽을 적는다.
diff --git a/docs/research/140-spec-suite.md b/docs/research/140-spec-suite.md
index 24d488a..4b799df 100644
--- a/docs/research/140-spec-suite.md
+++ b/docs/research/140-spec-suite.md
@@ -118,6 +118,66 @@
- **메시지의 행은 중첩의 땜질인가?** 레코드의 리스트를 메시지에만 허락한 것은 중첩이 없어서 낸 길로 보인다. 중첩이 들어오면 따로 둘
까닭이 없다.
+## 027의 1단계: SHA-256 (2026-10-13)
+
+[027](../design/027-computation-core.md)의 1단계를 만들었다.
+- **Pure.** 명령 넷이다.
+ - `I64_BITS`: and, or, xor, 두 시프트. `~x`는 xor −1이다.
+ - `BYTE_AT`: 자리의 바이트.
+ - `BYTES_OF`: i64 리스트의 바이트.
+ - `INDEX_AT`: 자리의 항목. 끝을 넘으면 멈춰 서지 않고 `not_found`다.
+- **aup.**
+ - 16진 리터럴, 파이썬의 우선순위를 따르는 비트 연산자, `b[i]`
+ - `list of i64`(`number`, `bool`, `text`)와 그 리터럴, 자리로 읽고 쓰기, `bytes(xs)`
+ - 한 번에 주기 `a, b = x, y`와 `let a, b = …`
+- **SHA-256.** aup의 `examples/sha256.aup`에 썼다.
+
+**맞음.** 공식 벡터를 Pure 안에서 돌려 맞는지 본다.
+- 대조한 메시지는 142개다: FIPS 180-2의 예 넷("", "abc", 448비트, 896비트), 채움의 모든 경계를 덮는 길이 0–129, 255–32,000바이트.
+- 다이제스트가 세 실행 길(묶음, 실행기만, 기계어)과 기본 길에서 모두 `hashlib`와 같다.
+- 이 대조는 Pure의 `make check`(`module_assumptions`, 메시지 135개)에 들어갔다.
+- aup의 검사에는 다음이 들어갔다.
+ - 진단 12건
+ - 받아들이는 꼴 하나
+ - 무작위 비트 식 192개: 사건 768개, 그중 넘침과 한도 실패 196개가 파이썬의 정수와 같다
+ - 바이트와 리스트의 앱 하나
+- Pure 단위 시험(`bits_and_bytes`)은 비트 연산의 끝값 27개, 바이트 읽기 11개, 거절 7개를 본다.
+
+**막대.**
+
+| 막대 | 잰 것 | |
+|---|---|---|
+| 공식 벡터 모두 | 142/142, 세 길 | 지남 |
+| 줄 수 ≤ 파이썬 참조 | aup 52줄(닫는 괄호만인 줄 7) 대 파이썬 28줄 | **못 미침** |
+| C 대비 시간 | 65,536바이트에서 바이트당 1,591 ns 대 C 9.0 ns: **176배**. 순수 파이썬은 3,708 ns(Pure가 2.3배 빠름) | 기록(막대 없음) |
+| 기존 일의 명령 수 | 아래 | 아래 |
+
+원본: `research/experiments/044-specs/`
+- `check.py`: 맞음과 줄 수
+- `time.c`: 같은 바이트로 C와 견준 시간. 다이제스트가 같아야 잰다.
+- `sha256.py`: 파이썬 참조
+- `2026-10-13-sha256.jsonl`: 잰 수
+
+**줄을 길게 한 것.** 처음 쓴 것은 72줄이었다. 한 번에 주기가 없어서 상태 여덟 개를 옮기는 데만 24줄이 들었다. 그것을 1단계에
+넣어(싸고 범용이다) 52줄이 됐다. 남은 차이는 다음이다.
+- 형식 지정이 없다. 16진 다이제스트에 7줄이 들었고, 파이썬은 `f'{x:08x}'` 한 줄이다.
+- 바이트를 잇거나 자르지 못한다. 채움에 fn 하나와 바이트마다의 조건이 들었고, 낱말 만들기에 5줄이 들었다. 파이썬은 `data + b'\x80' + …`와
+ `int.from_bytes` 한 줄씩이다.
+- 리스트를 값으로 짓지 못한다. 메시지 스케줄을 블록마다 다시 만들지 못하고, 64칸을 미리 채워 덮어쓴다.
+- `let`의 리스트는 핸들러 맨 바깥에만 둔다. 그래서 해시값을 리스트 대신 이름 여덟 개에 두었다.
+- 닫는 괄호의 줄이 7줄이다.
+
+앞의 넷은 값의 일이라 2단계에 넣는다. 027의 2단계에 형식 지정(16진, 자릿수), 바이트 잇기와 자르기, 값으로서의 리스트를 더한다.
+
+**시간이 가는 곳.** 블록 하나에 Pure 명령 19,129개가 든다.
+- 비트 연산이 21%다.
+- fn 호출의 몫(인자를 꺼내는 PICK 16%, 리터럴 CONST 16%, 시프트 수를 바꾸는 I64_FROM 7%)이 39%다.
+- `rotr(x, 7)`의 7은 상수인데도 fn을 펼칠 때 접히지 않아, `32 - n`을 매번 수로 계산하고 i64로 바꾼다. 인라인에서 리터럴 인자를 접으면
+ 명령의 3분의 1 남짓이 줄 것으로 본다. 컴파일러의 구현 일이다.
+- 명령 하나에 약 5.3 ns가 든다. 값의 칸(`Slot`)이 바이트 참조를 함께 들어 PICK마다 48바이트를 옮기는 꼴이다.
+- 기계어 계층은 이 루프를 빠르게 하지 못했다. `PURE_JIT=all`이어도 시간이 같다. 새 명령마다 실행기의 걸음을 불러서다.
+- 바이트와 비트의 일을 빠르게 하는 것은 핵이 선 뒤의 일이다(027). 그 과녁은 이제 수치 루프가 아니라 이런 일이다.
+
## 착수 기준 (다음 일)
- **바꿀 결정.** 계산의 핵을 설계한다(027). 위 표의 여덟 후보 가운데 무엇을 어떤 모양으로 받을지 정한다.
diff --git a/research/experiments/044-specs/2026-10-13-sha256.jsonl b/research/experiments/044-specs/2026-10-13-sha256.jsonl
new file mode 100644
index 0000000..b8d3c7c
--- /dev/null
+++ b/research/experiments/044-specs/2026-10-13-sha256.jsonl
@@ -0,0 +1,6 @@
+{"size":65536,"jit":"0","batch":"0","c_ns_per_byte":9.035,"pure_ns_per_byte":1592.087,"ratio":176.2,"instructions":19606989,"instructions_per_block":19129}
+{"size":65536,"jit":"0","batch":"","c_ns_per_byte":9.049,"pure_ns_per_byte":1589.872,"ratio":175.7,"instructions":19606989,"instructions_per_block":19129}
+{"size":65536,"jit":"all","batch":"","c_ns_per_byte":9.031,"pure_ns_per_byte":1592.166,"ratio":176.3,"instructions":19606989,"instructions_per_block":19129}
+{"size":65536,"jit":"","batch":"","c_ns_per_byte":9.051,"pure_ns_per_byte":1591.118,"ratio":175.8,"instructions":19606989,"instructions_per_block":19129}
+{"size":65536,"python_ns_per_byte":3708.3}
+{"messages":142,"right":{"batches":142,"rounds":142,"all":142,"default":142},"aup_lines":52,"aup_brace_lines":7,"python_lines":28}
diff --git a/research/experiments/044-specs/check.py b/research/experiments/044-specs/check.py
new file mode 100755
index 0000000..bc43090
--- /dev/null
+++ b/research/experiments/044-specs/check.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+"""Research 140 / design 027 stage 1: SHA-256 written in aup (aup's examples/sha256.aup).
+
+Builds it with aup (AUP, default the sibling checkout), runs FIPS 180-2's
+examples and messages of every length 0-129 and some longer ones on Pure's
+three paths (batches, rounds, all compiled) and the default, each digest
+against hashlib; then counts lines (code lines: neither blank nor a comment;
+brace lines: a closing brace alone) of sha256.aup and of sha256.py's
+algorithm (above its main). One JSON line."""
+import hashlib, json, os, random, subprocess, sys, tempfile
+
+here = os.path.dirname(os.path.abspath(__file__))
+pure = os.path.normpath(os.path.join(here, '../../..'))
+aup = os.environ.get('AUP', os.path.join(pure, '..', 'aup'))
+
+def lines(path, stop=None):
+ code = braces = 0
+ for line in open(path):
+ if stop and line.startswith(stop): break
+ text = line.strip()
+ if not text or text.startswith('#'): continue
+ code += 1
+ braces += text == '}'
+ return code, braces
+
+random.seed(140)
+messages = [b'', b'abc', b'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq',
+ b'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu']
+messages += [bytes(random.randrange(256) for _ in range(n)) for n in list(range(130)) + [255, 256, 511, 512, 1000, 4096, 10000, 32000]]
+with tempfile.TemporaryDirectory() as tmp:
+ module = os.path.join(tmp, 'sha256.pmod')
+ subprocess.run([os.path.join(aup, 'build', 'aup'), 'build', os.path.join(aup, 'examples', 'sha256.aup'), '-o', module], check=True)
+ given = ''.join(f"message hash {m.hex() or '-'}\n" for m in messages).encode()
+ paths = {}
+ for name, env in (('batches', {'PURE_JIT': '0'}), ('rounds', {'PURE_JIT': '0', 'PURE_BATCH': '0'}),
+ ('all', {'PURE_JIT': 'all'}), ('default', {})):
+ out = subprocess.run([os.path.join(pure, 'build', 'pure-run'), module], input=given, capture_output=True,
+ env=dict(os.environ, **env), check=True).stdout.decode().splitlines()
+ paths[name] = sum(json.loads(o)['states']['digest'] == hashlib.sha256(m).hexdigest() and json.loads(o)['fault'] == 'none'
+ for m, o in zip(messages, out)) if len(out) == len(messages) else 0
+aup_code, aup_braces = lines(os.path.join(aup, 'examples', 'sha256.aup'))
+python_code, _ = lines(os.path.join(here, 'sha256.py'), stop='if __name__')
+ok = all(n == len(messages) for n in paths.values())
+print(json.dumps({'messages': len(messages), 'right': paths, 'aup_lines': aup_code, 'aup_brace_lines': aup_braces,
+ 'python_lines': python_code}))
+sys.exit(0 if ok else 1)
diff --git a/research/experiments/044-specs/sha256.py b/research/experiments/044-specs/sha256.py
new file mode 100644
index 0000000..0ea3eee
--- /dev/null
+++ b/research/experiments/044-specs/sha256.py
@@ -0,0 +1,46 @@
+# SHA-256 (FIPS 180-4) in plain Python: the reference 140 counts aup's lines against.
+K = [
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+]
+H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]
+
+
+def rotr(x, n):
+ return (x >> n | x << (32 - n)) & 0xffffffff
+
+
+def sha256(data):
+ h = list(H)
+ padded = data + b'\x80' + b'\x00' * ((55 - len(data)) % 64) + (len(data) * 8).to_bytes(8, 'big')
+ for at in range(0, len(padded), 64):
+ w = [int.from_bytes(padded[at + 4 * t:at + 4 * t + 4], 'big') for t in range(16)]
+ for t in range(16, 64):
+ x, y = w[t - 15], w[t - 2]
+ w.append((w[t - 16] + (rotr(x, 7) ^ rotr(x, 18) ^ x >> 3) + w[t - 7] + (rotr(y, 17) ^ rotr(y, 19) ^ y >> 10)) & 0xffffffff)
+ a, b, c, d, e, f, g, hh = h
+ for t in range(64):
+ t1 = (hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[t] + w[t]) & 0xffffffff
+ t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c)) & 0xffffffff
+ a, b, c, d, e, f, g, hh = (t1 + t2) & 0xffffffff, a, b, c, (d + t1) & 0xffffffff, e, f, g
+ h = [(x + y) & 0xffffffff for x, y in zip(h, [a, b, c, d, e, f, g, hh])]
+ return ''.join(f'{x:08x}' for x in h)
+
+
+if __name__ == '__main__':
+ import hashlib, random, sys, time
+ size = int(sys.argv[1]) if len(sys.argv) > 1 else 65536
+ data = bytes(random.Random(140).randrange(256) for _ in range(size))
+ assert sha256(data) == hashlib.sha256(data).hexdigest()
+ runs = []
+ for _ in range(7):
+ start = time.perf_counter()
+ sha256(data)
+ runs.append((time.perf_counter() - start) * 1e9 / size)
+ print('{"size":%d,"python_ns_per_byte":%.1f}' % (size, sorted(runs)[3]))
diff --git a/research/experiments/044-specs/time.c b/research/experiments/044-specs/time.c
new file mode 100644
index 0000000..248889b
--- /dev/null
+++ b/research/experiments/044-specs/time.c
@@ -0,0 +1,120 @@
+/* Research 140, design 027 stage 1: SHA-256 in aup (aup's examples/sha256.aup, run by Pure)
+ * against the same algorithm in C, on the same bytes. The digests must be the
+ * same; then each is timed (the median of seven runs of rounds digests).
+ *
+ * cc -O2 -std=c17 -I../../../core time.c ../../../build/libpure.a -lm -pthread -o time
+ * ./time sha256.pmod SIZE ROUNDS (PURE_JIT and PURE_BATCH choose Pure's path)
+ *
+ * One JSON line: the size, the C and Pure nanoseconds per byte, their ratio and
+ * the instructions Pure counted for one digest. */
+#define _POSIX_C_SOURCE 200809L
+#include "module.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* ---- SHA-256 in C, as FIPS 180-4 writes it (no SHA instructions: the same algorithm) */
+static const uint32_t K[64] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
+
+static uint32_t rotr(uint32_t x, unsigned n) { return x >> n | x << (32 - n); }
+
+static void block(uint32_t h[8], const unsigned char *p) {
+ uint32_t w[64];
+ for (int t = 0; t < 16; ++t) w[t] = (uint32_t)p[4 * t] << 24 | (uint32_t)p[4 * t + 1] << 16 | (uint32_t)p[4 * t + 2] << 8 | p[4 * t + 3];
+ for (int t = 16; t < 64; ++t) {
+ uint32_t s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ w[t - 15] >> 3;
+ uint32_t s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ w[t - 2] >> 10;
+ w[t] = w[t - 16] + s0 + w[t - 7] + s1;
+ }
+ uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
+ for (int t = 0; t < 64; ++t) {
+ uint32_t t1 = hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[t] + w[t];
+ uint32_t t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c));
+ hh = g, g = f, f = e, e = d + t1, d = c, c = b, b = a, a = t1 + t2;
+ }
+ h[0] += a, h[1] += b, h[2] += c, h[3] += d, h[4] += e, h[5] += f, h[6] += g, h[7] += hh;
+}
+
+static void sha256(const unsigned char *data, size_t size, char hex[65]) {
+ uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
+ size_t whole = size / 64 * 64;
+ for (size_t at = 0; at < whole; at += 64) block(h, data + at);
+ unsigned char last[128] = {0};
+ size_t left = size - whole, tail = left + 9 <= 64 ? 64 : 128;
+ memcpy(last, data + whole, left);
+ last[left] = 0x80;
+ for (int i = 0; i < 8; ++i) last[tail - 1 - i] = (unsigned char)((uint64_t)size * 8 >> (8 * i));
+ for (size_t at = 0; at < tail; at += 64) block(h, last + at);
+ for (int i = 0; i < 8; ++i) snprintf(hex + 8 * i, 9, "%08x", h[i]);
+}
+
+static double now_ns(void) {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return (double)t.tv_sec * 1e9 + (double)t.tv_nsec;
+}
+
+static int by_value(const void *a, const void *b) {
+ double x = *(const double *)a, y = *(const double *)b;
+ return (x > y) - (x < y);
+}
+
+typedef struct { char text[65]; size_t used; } Hex;
+static bool gather(void *context, PureSpan span) {
+ Hex *h = context;
+ for (size_t i = 0; i < span.size && h->used < 64; ++i) h->text[h->used++] = (char)span.data[i];
+ return true;
+}
+
+int main(int argc, char **argv) {
+ if (argc != 4) { fprintf(stderr, "usage: %s MODULE SIZE ROUNDS\n", argv[0]); return 2; }
+ size_t size = strtoul(argv[2], NULL, 10);
+ int rounds = atoi(argv[3]);
+ unsigned char *data = malloc(size ? size : 1);
+ uint32_t seed = 140;
+ for (size_t i = 0; i < size; ++i) seed = seed * 1664525u + 1013904223u, data[i] = (unsigned char)(seed >> 24);
+
+ PureModule module;
+ char error[160];
+ if (pure_module_read(argv[1], &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
+ PureApp app;
+ if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
+ int hash = pure_module_input(&module, "hash");
+ char want[65];
+ sha256(data, size, want);
+ if (pure_app_message(&app, (uint32_t)hash, NULL, -1, (PureSpan){data, size}) != PURE_OK) { fprintf(stderr, "event failed\n"); return 1; }
+ uint64_t work = app.work;
+ Hex got = {{0}, 0};
+ pure_draft_read(app.texts[0], 0, pure_draft_size(app.texts[0]), gather, &got);
+ if (strcmp(got.text, want)) { fprintf(stderr, "digests differ: %s %s\n", got.text, want); return 1; }
+
+ double c_runs[7], pure_runs[7];
+ volatile char sink = 0;
+ for (int r = 0; r < 7; ++r) {
+ double start = now_ns();
+ for (int i = 0; i < rounds; ++i) { char hex[65]; sha256(data, size, hex); sink ^= hex[i % 64]; }
+ c_runs[r] = (now_ns() - start) / rounds;
+ start = now_ns();
+ for (int i = 0; i < rounds; ++i)
+ if (pure_app_message(&app, (uint32_t)hash, NULL, -1, (PureSpan){data, size}) != PURE_OK) return 1;
+ pure_runs[r] = (now_ns() - start) / rounds;
+ }
+ qsort(c_runs, 7, sizeof(double), by_value);
+ qsort(pure_runs, 7, sizeof(double), by_value);
+ const char *jit = getenv("PURE_JIT"), *batch = getenv("PURE_BATCH");
+ printf("{\"size\":%zu,\"jit\":\"%s\",\"batch\":\"%s\",\"c_ns_per_byte\":%.3f,\"pure_ns_per_byte\":%.3f,\"ratio\":%.1f,"
+ "\"instructions\":%llu,\"instructions_per_block\":%.0f}\n",
+ size, jit ? jit : "", batch ? batch : "", c_runs[3] / (double)size, pure_runs[3] / (double)size,
+ pure_runs[3] / c_runs[3], (unsigned long long)work, (double)work / (double)(size / 64 + 1));
+ pure_app_stop(&app);
+ return 0;
+}
diff --git a/tests/check.py b/tests/check.py
index 91ac114..eef4ae9 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -1841,6 +1841,24 @@ def module_assumptions(aup, pure, work):
assert outputs[name] == outputs['batches'], ('view', name, next((a, b) for a, b in zip(outputs[name].splitlines(), outputs['batches'].splitlines()) if a != b))
faults = [json.loads(line)['fault'] for line in outputs['batches'].splitlines() if line.startswith('{"states"')]
assert 'none' in faults and 'work' in faults, faults
+ # SHA-256 written in aup (its examples/sha256.aup, docs/design/027's stage 1: bytes by position, bit operations, lists
+ # of i64): FIPS 180-2's examples and messages of every length up to two blocks, against hashlib, on every path.
+ import hashlib
+ import random
+ program = work / 'sha256.pmod'
+ compile_module(aup, EDITOR.parent / 'sha256.aup', program)
+ chooser = random.Random(140)
+ messages = [b'abc', b'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq',
+ b'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu']
+ messages += [bytes(chooser.randrange(256) for _ in range(n)) for n in list(range(130)) + [1000, 4096]]
+ given = ''.join(f"message hash {m.hex() or '-'}\n" for m in messages)
+ outputs = {name: subprocess.run([str(pure / 'build/pure-run'), str(program)], input=given, capture_output=True, text=True,
+ timeout=120, env=dict(os.environ, **env)).stdout
+ for name, env in (('batches', {'PURE_JIT': '0'}), ('rounds', {'PURE_JIT': '0', 'PURE_BATCH': '0'}), ('all', {'PURE_JIT': 'all'}))}
+ for name in ('rounds', 'all'):
+ assert outputs[name] == outputs['batches'], ('sha256', name)
+ digests = [json.loads(line)['states']['digest'] for line in outputs['batches'].splitlines()]
+ assert digests == [hashlib.sha256(m).hexdigest() for m in messages], 'sha256'
return len(runs)