リポジトリ / emssr / independent_common_compiler
r2
| r2 emssr | 1 | # -*- coding: utf-8 -*- |
| r2 emssr | 2 | """lane64 の三本めの道。W 枚の lane を numpy の配列に束ね、block を一度に走らせる。 |
| r2 emssr | 3 | |
| r2 emssr | 4 | doc: casm.lane64.sem casm.lane64.merge casm.lane64.cost casm.simd |
| r2 emssr | 5 | |
| r2 emssr | 6 | 解釈器 vcpu.Interp 式 (2.1) の mask 付き意味。これが定義になる |
| r2 emssr | 7 | 翻訳器 vcpu.run_lane block を Python の code object へ落とす。一枚 |
| r2 emssr | 8 | 束ね ここ 同じ block を走る lane を配列の一次元に並べる |
| r2 emssr | 9 | |
| r2 emssr | 10 | 命題 6.1 が束ねの根拠になる。lane の作業領域は式 (1.2) で交わらないので、 |
| r2 emssr | 11 | (mask, pc) の組を pc 昇順に進める観測は、lane を一本ずつ走らせた観測に一致する。 |
| r2 emssr | 12 | だから組を「lane の添字」で持ってよく、mask の bit 演算は要らない。 |
| r2 emssr | 13 | |
| r2 emssr | 14 | 20 章 §0.4 は「大整数の欄へ W 個を詰める」形を退けている。乗算が欄をまたいで |
| r2 emssr | 15 | 畳み込み、LD と ST は lane ごとに番地が違うためになる。配列はその制限を受けない。 |
| r2 emssr | 16 | gather と scatter が LD と ST をそのまま運ぶので、頭打ちの式 (0.3) は効かない。 |
| r2 emssr | 17 | |
| r2 emssr | 18 | 実測(CPython 3.12 + numpy 2.5、一 lane あたり ns) |
| r2 emssr | 19 | |
| r2 emssr | 20 | 種 list W=256 W=1024 倍 |
| r2 emssr | 21 | ADD 37.3 3.54 1.43 26 |
| r2 emssr | 22 | MUL 35.5 3.48 1.44 25 |
| r2 emssr | 23 | LD 定 26.9 1.46 0.54 50 番地が定数。M の行がそのまま連続になる |
| r2 emssr | 24 | ST 定 28.9 1.56 0.53 55 |
| r2 emssr | 25 | LD 変 26.9 10.21 5.87 4.6 番地がワイヤの値。gather になる |
| r2 emssr | 26 | ST 変 28.9 8.56 4.94 5.9 |
| r2 emssr | 27 | 比較 30.8 3.17 1.18 26 |
| r2 emssr | 28 | |
| r2 emssr | 29 | 作業領域は (語数, W) に取る。(W, 語数) では定数番地が stride になり、可変番地の |
| r2 emssr | 30 | gather も遅くなる(W=1024 で 5.95 対 7.80 ns)。 |
| r2 emssr | 31 | |
| r2 emssr | 32 | 重さ = 語数 x W x 8 byte。W=256 で 語数 70000 なら 143 MB |
| r2 emssr | 33 | |
| r2 emssr | 34 | 語は Z(多倍長)が仕様(§0.3)だが、配列は int64 になる。だから `overflow` が |
| r2 emssr | 35 | 溢れを見る。溢れたら翻訳器の道へ落とす。証とソルバーの語は在庫・金・手数・ |
| r2 emssr | 36 | 区画の番号なので 2^62 に遠く及ばない。commonc の compile はここへ載せない。 |
| r2 emssr | 37 | """ |
| r2 emssr | 38 | |
| r2 emssr | 39 | import numpy as np |
| r2 emssr | 40 | |
| r2 emssr | 41 | import vcpu |
| r2 emssr | 42 | from vcpu import (ADD, EQ, HALT, JMP, KO, LD, LT, MAT, MUL, NEG, OK, PUT, ST, |
| r2 emssr | 43 | LANDMARKS, NAME, R_END, R_KO, R_OK, blocks) |
| r2 emssr | 44 | |
| r2 emssr | 45 | GUARD = 1 << 62 # これを越えた語が出たら int64 の巻き戻しを疑う |
| r2 emssr | 46 | |
| r2 emssr | 47 | |
| r2 emssr | 48 | class LaneVec(object): |
| r2 emssr | 49 | """W 枚の lane(式 1.1 の Σ_lane64 を W 本)。 |
| r2 emssr | 50 | |
| r2 emssr | 51 | R[a] が landmark a の lane 方向のベクトル、M[c] が語 c の lane 方向のベクトルになる。 |
| r2 emssr | 52 | どちらも第一軸が番号なので、定数番地は連続した W 語になる。 |
| r2 emssr | 53 | """ |
| r2 emssr | 54 | |
| r2 emssr | 55 | def __init__(self, width, words): |
| r2 emssr | 56 | self.W = width |
| r2 emssr | 57 | self.R = np.zeros((LANDMARKS, width), dtype=np.int64) |
| r2 emssr | 58 | self.M = np.zeros((words, width), dtype=np.int64) |
| r2 emssr | 59 | self.ix = np.arange(width, dtype=np.int64) |
| r2 emssr | 60 | self.OUT = [[] for _ in range(width)] |
| r2 emssr | 61 | self.report = [None] * width |
| r2 emssr | 62 | self.blocks = 0 |
| r2 emssr | 63 | |
| r2 emssr | 64 | def seed(self, addr, values): |
| r2 emssr | 65 | """語 addr へ lane ごとの値を置く。put_input と違い、こちらは一語だけ動かす""" |
| r2 emssr | 66 | self.M[addr] = values |
| r2 emssr | 67 | |
| r2 emssr | 68 | def put_input(self, data, base): |
| r2 emssr | 69 | """18 章 §7.1 と同じ置き方(式 8.2)。全 lane へ同じ入力を配る""" |
| r2 emssr | 70 | n = min(len(data), self.M.shape[0] - base) |
| r2 emssr | 71 | self.M[0] = n |
| r2 emssr | 72 | self.M[1] = base |
| r2 emssr | 73 | self.M[base:base + n] = np.asarray(data[:n], dtype=np.int64)[:, None] |
| r2 emssr | 74 | return n |
| r2 emssr | 75 | |
| r2 emssr | 76 | def overflow(self): |
| r2 emssr | 77 | """int64 の巻き戻しを疑う語があるか。無ければ False""" |
| r2 emssr | 78 | return bool(np.abs(self.R).max() >= GUARD or np.abs(self.M).max() >= GUARD) |
| r2 emssr | 79 | |
| r2 emssr | 80 | |
| r2 emssr | 81 | # ===== 分岐 ===== |
| r2 emssr | 82 | # |
| r2 emssr | 83 | # 閉じの判定は lane ごとに割れる。三通りを返す。 |
| r2 emssr | 84 | # |
| r2 emssr | 85 | # 全部が真 行き先ひとつ |
| r2 emssr | 86 | # 全部が偽 行き先ひとつ |
| r2 emssr | 87 | # 割れた (真の lane の印, 真の行き先, 偽の行き先) |
| r2 emssr | 88 | # |
| r2 emssr | 89 | # 割れた先は run_vec が組へ足す。命題 6.1 により pc 昇順で進めれば観測は変わらない。 |
| r2 emssr | 90 | |
| r2 emssr | 91 | def _br(cond, yes, no): |
| r2 emssr | 92 | if cond.all(): |
| r2 emssr | 93 | return yes |
| r2 emssr | 94 | if not cond.any(): |
| r2 emssr | 95 | return no |
| r2 emssr | 96 | return (cond, yes, no) |
| r2 emssr | 97 | |
| r2 emssr | 98 | |
| r2 emssr | 99 | # ===== block を numpy の文へ落とす ===== |
| r2 emssr | 100 | # |
| r2 emssr | 101 | # 局所の値番号付けは翻訳器(vcpu._block)と同じ。違いは三つだけになる。 |
| r2 emssr | 102 | # |
| r2 emssr | 103 | # landmark R[a][s] s は組の印。全 lane なら slice(None) で view になる |
| r2 emssr | 104 | # 作業領域 M[c][s] 番地が定数のとき。連続した W 語 |
| r2 emssr | 105 | # M[addr, ixs] 番地がワイヤの値のとき。gather / scatter |
| r2 emssr | 106 | # 即値 そのまま置く。numpy が broadcast する |
| r2 emssr | 107 | |
| r2 emssr | 108 | def _block_vec(prog, k, body, term, stop, idx, n, out_live=None, dead=None, scan=None): |
| r2 emssr | 109 | steps = list(body) + ([] if term is None else [term]) |
| r2 emssr | 110 | reg, mem, tmp = {}, {}, [0] |
| r2 emssr | 111 | out = ['def _v%d(R, M, OUT, ixs, s):' % k] |
| r2 emssr | 112 | |
| r2 emssr | 113 | # 定数番地の Load は M[c][s] になる。s が slice(None) のとき、これは写しではなく |
| r2 emssr | 114 | # view になる。同じ block が後で M[c][s] へ書くと、先に読んだ名前の中身が一緒に |
| r2 emssr | 115 | # 動く。翻訳器の道では M[c] が Python の int なので起きない。 |
| r2 emssr | 116 | # |
| r2 emssr | 117 | # t1 = M[A][s] view |
| r2 emssr | 118 | # M[A][s] = t2 |
| r2 emssr | 119 | # M[B][s] = t1 + t2 t1 は既に t2。Fibonacci が 2 の冪へ化ける |
| r2 emssr | 120 | # |
| r2 emssr | 121 | # 後ろに Store がある Load だけ写しを取る。番地がワイヤの値のときは |
| r2 emssr | 122 | # fancy indexing が写しを返すので、そのままでよい。 |
| r2 emssr | 123 | last_st = -1 |
| r2 emssr | 124 | for _p, _i in enumerate(body): |
| r2 emssr | 125 | if prog[_i][0] == ST: |
| r2 emssr | 126 | last_st = _p |
| r2 emssr | 127 | |
| r2 emssr | 128 | def get(r): |
| r2 emssr | 129 | if r not in reg: |
| r2 emssr | 130 | reg[r] = 'R[%d][s]' % r |
| r2 emssr | 131 | return reg[r] |
| r2 emssr | 132 | |
| r2 emssr | 133 | def lit(e): |
| r2 emssr | 134 | try: |
| r2 emssr | 135 | return int(e) |
| r2 emssr | 136 | except ValueError: |
| r2 emssr | 137 | return None |
| r2 emssr | 138 | |
| r2 emssr | 139 | def shifted(r, sh): |
| r2 emssr | 140 | e = get(r) |
| r2 emssr | 141 | v = lit(e) |
| r2 emssr | 142 | if sh == 0: |
| r2 emssr | 143 | return e |
| r2 emssr | 144 | if v is not None: |
| r2 emssr | 145 | return str(v << sh) |
| r2 emssr | 146 | return '(%s<<%d)' % (e, sh) |
| r2 emssr | 147 | |
| r2 emssr | 148 | def fresh(expr): |
| r2 emssr | 149 | tmp[0] += 1 |
| r2 emssr | 150 | name = 't%d' % tmp[0] |
| r2 emssr | 151 | out.append(' %s=%s' % (name, expr)) |
| r2 emssr | 152 | return name |
| r2 emssr | 153 | |
| r2 emssr | 154 | def fold2(x, y, f, sym): |
| r2 emssr | 155 | vx, vy = lit(x), lit(y) |
| r2 emssr | 156 | if vx is not None and vy is not None: |
| r2 emssr | 157 | return str(f(vx, vy)) |
| r2 emssr | 158 | return fresh('%s%s%s' % (x, sym, y)) |
| r2 emssr | 159 | |
| r2 emssr | 160 | for pos, i in enumerate(body): |
| r2 emssr | 161 | op, a, b, c, sb, sc, im = prog[i] |
| r2 emssr | 162 | if op == MAT: |
| r2 emssr | 163 | reg[a] = str(im) |
| r2 emssr | 164 | elif op == ADD: |
| r2 emssr | 165 | reg[a] = fold2(shifted(b, sb), shifted(c, sc), lambda x, y: x + y, '+') |
| r2 emssr | 166 | elif op == MUL: |
| r2 emssr | 167 | reg[a] = fold2(get(b), get(c), lambda x, y: x * y, '*') |
| r2 emssr | 168 | elif op == NEG: |
| r2 emssr | 169 | e = get(b) |
| r2 emssr | 170 | v = lit(e) |
| r2 emssr | 171 | reg[a] = str(-v) if v is not None else fresh('-%s' % e) |
| r2 emssr | 172 | elif op == LD: |
| r2 emssr | 173 | addr = get(b) |
| r2 emssr | 174 | v = lit(addr) |
| r2 emssr | 175 | if v is not None and v in mem: |
| r2 emssr | 176 | reg[a] = mem[v] |
| r2 emssr | 177 | else: |
| r2 emssr | 178 | if scan is not None: |
| r2 emssr | 179 | scan.add(v) |
| r2 emssr | 180 | reg[a] = fresh(('M[%s][s].copy()' if pos < last_st else 'M[%s][s]') % addr |
| r2 emssr | 181 | if v is not None else 'M[%s,ixs]' % addr) |
| r2 emssr | 182 | if v is not None: |
| r2 emssr | 183 | mem[v] = reg[a] |
| r2 emssr | 184 | elif op == ST: |
| r2 emssr | 185 | addr, val = get(b), get(c) |
| r2 emssr | 186 | v = lit(addr) |
| r2 emssr | 187 | if not (dead is not None and v in dead): |
| r2 emssr | 188 | out.append(' M[%s][s]=%s' % (addr, val) if v is not None |
| r2 emssr | 189 | else ' M[%s,ixs]=%s' % (addr, val)) |
| r2 emssr | 190 | if v is not None: |
| r2 emssr | 191 | mem[v] = val |
| r2 emssr | 192 | else: |
| r2 emssr | 193 | mem.clear() |
| r2 emssr | 194 | elif op == PUT: |
| r2 emssr | 195 | # 出力は lane ごとに別の列になるので、ここだけは Python の loop になる。 |
| r2 emssr | 196 | # ixs が lane の実番号を持つので、組が割れていても行き先を取り違えない |
| r2 emssr | 197 | out.append(' _o=%s' % get(b)) |
| r2 emssr | 198 | out.append(' for _p in range(len(ixs)): OUT[int(ixs[_p])].append(int(_o[_p])&255)') |
| r2 emssr | 199 | else: |
| r2 emssr | 200 | raise ValueError('直線部分に置けない箱: %s' % NAME[op]) |
| r2 emssr | 201 | |
| r2 emssr | 202 | keep = set(reg) if out_live is None else (set(reg) & out_live) |
| r2 emssr | 203 | for r in sorted(keep): |
| r2 emssr | 204 | if reg[r] != 'R[%d][s]' % r: |
| r2 emssr | 205 | out.append(' R[%d][s]=%s' % (r, reg[r])) |
| r2 emssr | 206 | |
| r2 emssr | 207 | def to(t): |
| r2 emssr | 208 | return idx[t] if 0 <= t < n and t in idx else R_END |
| r2 emssr | 209 | |
| r2 emssr | 210 | if term is None: |
| r2 emssr | 211 | out.append(' return %d' % (idx[stop] if stop < n else R_END)) |
| r2 emssr | 212 | else: |
| r2 emssr | 213 | op, _a, b, c, sb, sc, im = prog[term] |
| r2 emssr | 214 | if op == OK: |
| r2 emssr | 215 | out.append(' return %d' % R_OK) |
| r2 emssr | 216 | elif op == KO: |
| r2 emssr | 217 | out.append(' return %d' % R_KO) |
| r2 emssr | 218 | elif op in (HALT,): |
| r2 emssr | 219 | out.append(' return %d' % R_END) |
| r2 emssr | 220 | elif op == JMP: |
| r2 emssr | 221 | out.append(' return %d' % to(term + im)) |
| r2 emssr | 222 | else: |
| r2 emssr | 223 | x, y = shifted(b, sb), shifted(c, sc) |
| r2 emssr | 224 | vx, vy = lit(x), lit(y) |
| r2 emssr | 225 | if vx is not None and vy is not None: |
| r2 emssr | 226 | taken = (vx == vy) if op == EQ else (vx < vy) |
| r2 emssr | 227 | out.append(' return %d' % (to(term + im) if taken else to(term + 1))) |
| r2 emssr | 228 | else: |
| r2 emssr | 229 | rel = '==' if op == EQ else '<' |
| r2 emssr | 230 | out.append(' return _br(%s%s%s,%d,%d)' % (x, rel, y, to(term + im), to(term + 1))) |
| r2 emssr | 231 | return out + [''] |
| r2 emssr | 232 | |
| r2 emssr | 233 | |
| r2 emssr | 234 | def translate(prog, wires=None): |
| r2 emssr | 235 | """image を numpy の block 関数の表へ落とす。引数は vcpu.translate と同じ意味になる。 |
| r2 emssr | 236 | |
| r2 emssr | 237 | wires = (lo, hi) はワイヤの区間。他のどの block も読まない Store を落とす。 |
| r2 emssr | 238 | """ |
| r2 emssr | 239 | ent, idx, bl = blocks(prog) |
| r2 emssr | 240 | n = len(prog) |
| r2 emssr | 241 | live = vcpu._liveness(prog, bl, idx, n) |
| r2 emssr | 242 | dead = None |
| r2 emssr | 243 | if wires is not None: |
| r2 emssr | 244 | lo, hi = wires |
| r2 emssr | 245 | read = set() |
| r2 emssr | 246 | for k, (pc, body, term, stop) in enumerate(bl): |
| r2 emssr | 247 | _block_vec(prog, k, body, term, stop, idx, n, live[k], scan=read) |
| r2 emssr | 248 | dead = set(c for c in range(lo, hi) if c not in read) |
| r2 emssr | 249 | lines = [] |
| r2 emssr | 250 | for k, (pc, body, term, stop) in enumerate(bl): |
| r2 emssr | 251 | lines += _block_vec(prog, k, body, term, stop, idx, n, live[k], dead=dead) |
| r2 emssr | 252 | lines.append('TAB = (' + ''.join('_v%d,' % k for k in range(len(bl))) + ')') |
| r2 emssr | 253 | src = chr(10).join(lines) |
| r2 emssr | 254 | ns = {'_br': _br, 'np': np} |
| r2 emssr | 255 | exec(compile(src, '<lane64-vec>', 'exec'), ns) |
| r2 emssr | 256 | return src, ns['TAB'] |
| r2 emssr | 257 | |
| r2 emssr | 258 | |
| r2 emssr | 259 | # ===== 組を進める(命題 6.1)===== |
| r2 emssr | 260 | # |
| r2 emssr | 261 | # 組は (lane の印, block 番号) の対で持つ。印は slice(None) か添字の配列になる。 |
| r2 emssr | 262 | # slice(None) のときは R も M も view を返すので、fancy indexing の写しが出ない。 |
| r2 emssr | 263 | # 組が割れたら添字の配列になり、同じ block 番号の組は一つへ合わせる。 |
| r2 emssr | 264 | # |
| r2 emssr | 265 | # 「pc 昇順」は block 番号の昇順で足りる。block の入口は pc の昇順に並ぶので |
| r2 emssr | 266 | # (vcpu.entries が sorted を返す)、番号の順序が pc の順序に一致する。 |
| r2 emssr | 267 | |
| r2 emssr | 268 | _ALL = slice(None) |
| r2 emssr | 269 | |
| r2 emssr | 270 | |
| r2 emssr | 271 | def _join(groups, fresh): |
| r2 emssr | 272 | """同じ block 番号の組を合わせる(式 3.3 の reconvergence)""" |
| r2 emssr | 273 | for sel, b in fresh: |
| r2 emssr | 274 | for k, (s2, b2) in enumerate(groups): |
| r2 emssr | 275 | if b2 == b: |
| r2 emssr | 276 | if s2 is _ALL or sel is _ALL: |
| r2 emssr | 277 | groups[k] = (_ALL, b) |
| r2 emssr | 278 | else: |
| r2 emssr | 279 | groups[k] = (np.union1d(s2, sel), b) |
| r2 emssr | 280 | break |
| r2 emssr | 281 | else: |
| r2 emssr | 282 | groups.append((sel, b)) |
| r2 emssr | 283 | return groups |
| r2 emssr | 284 | |
| r2 emssr | 285 | |
| r2 emssr | 286 | def run(tab, lv, budget=1 << 32): |
| r2 emssr | 287 | """W 枚を束ねて走らせる。返りは lane ごとの報告の列。 |
| r2 emssr | 288 | |
| r2 emssr | 289 | 報告は翻訳器と同じ値を使う。0 が tu、42 が te、None が停止になる。 |
| r2 emssr | 290 | """ |
| r2 emssr | 291 | R, M, OUT, ix = lv.R, lv.M, lv.OUT, lv.ix |
| r2 emssr | 292 | groups = [(_ALL, 0)] |
| r2 emssr | 293 | steps = 0 |
| r2 emssr | 294 | while groups: |
| r2 emssr | 295 | steps += 1 |
| r2 emssr | 296 | if steps > budget: |
| r2 emssr | 297 | raise RuntimeError('組の歩数上限 %d を越えた' % budget) |
| r2 emssr | 298 | # 最も小さい block 番号の組を取る(命題 6.1) |
| r2 emssr | 299 | k = 0 if len(groups) == 1 else min(range(len(groups)), key=lambda i: groups[i][1]) |
| r2 emssr | 300 | sel, b = groups.pop(k) |
| r2 emssr | 301 | ixs = ix if sel is _ALL else ix[sel] |
| r2 emssr | 302 | r = tab[b](R, M, OUT, ixs, sel) |
| r2 emssr | 303 | if isinstance(r, tuple): |
| r2 emssr | 304 | cond, yes, no = r |
| r2 emssr | 305 | base = ix if sel is _ALL else ix[sel] |
| r2 emssr | 306 | groups = _join(groups, [(base[cond], yes), (base[~cond], no)]) |
| r2 emssr | 307 | continue |
| r2 emssr | 308 | if r >= 0: |
| r2 emssr | 309 | groups = _join(groups, [(sel, r)]) |
| r2 emssr | 310 | continue |
| r2 emssr | 311 | rep = 0 if r == R_OK else (42 if r == R_KO else None) |
| r2 emssr | 312 | if sel is _ALL: |
| r2 emssr | 313 | lv.report = [rep] * lv.W |
| r2 emssr | 314 | else: |
| r2 emssr | 315 | for l in sel: |
| r2 emssr | 316 | lv.report[int(l)] = rep |
| r2 emssr | 317 | lv.blocks = steps |
| r2 emssr | 318 | return lv.report |
| r2 emssr | 319 | |
| r2 emssr | 320 | |
| r2 emssr | 321 | # ===== 突き合わせ(命題 6.1 の検査)===== |
| r2 emssr | 322 | |
| r2 emssr | 323 | def agrees(image, seeds, words, need=None, watch=()): |
| r2 emssr | 324 | """束ねの道と翻訳器の道が同じ観測を返すか。 |
| r2 emssr | 325 | |
| r2 emssr | 326 | 命題 6.1 の最後が「二つの観測が一致することが、この package の検査の一つになる」 |
| r2 emssr | 327 | と言う。seeds は lane ごとの {番地: 値}。返りは (合うか, 束ねの報告, 翻訳器の報告)。 |
| r2 emssr | 328 | """ |
| r2 emssr | 329 | prog = vcpu.decode(image) |
| r2 emssr | 330 | W = len(seeds) |
| r2 emssr | 331 | lv = LaneVec(W, words) |
| r2 emssr | 332 | for l, s in enumerate(seeds): |
| r2 emssr | 333 | for a, v in s.items(): |
| r2 emssr | 334 | lv.M[a, l] = v |
| r2 emssr | 335 | _src, tabv = translate(prog, wires=(64, need) if need else None) |
| r2 emssr | 336 | rep_v = list(run(tabv, lv)) |
| r2 emssr | 337 | val_v = [[int(lv.M[a, l]) for a in watch] for l in range(W)] |
| r2 emssr | 338 | |
| r2 emssr | 339 | _src2, tab = vcpu.translate(prog, wires=(64, need) if need else None) |
| r2 emssr | 340 | rep_s, val_s = [], [] |
| r2 emssr | 341 | for s in seeds: |
| r2 emssr | 342 | lane = vcpu.Lane(words) |
| r2 emssr | 343 | for a, v in s.items(): |
| r2 emssr | 344 | lane.M[a] = v |
| r2 emssr | 345 | rep_s.append(vcpu.run_lane(tab, lane)) |
| r2 emssr | 346 | val_s.append([lane.M[a] for a in watch]) |
| r2 emssr | 347 | ok = (rep_v == rep_s and val_v == val_s and not lv.overflow()) |
| r2 emssr | 348 | return ok, rep_v, rep_s |