diff options
| -rw-r--r-- | docs/design/027-computation-core.md | 10 | ||||
| -rw-r--r-- | docs/research/140-spec-suite.md | 60 | ||||
| -rw-r--r-- | research/experiments/044-specs/2026-10-13-sha256.jsonl | 6 | ||||
| -rwxr-xr-x | research/experiments/044-specs/check.py | 46 | ||||
| -rw-r--r-- | research/experiments/044-specs/sha256.py | 46 | ||||
| -rw-r--r-- | research/experiments/044-specs/time.c | 120 | ||||
| -rw-r--r-- | tests/check.py | 18 |
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) |
