📦 Out of Context リポジトリ

リポジトリ / emssr / independent_common_compiler

r2
r2 emssr1
# -*- coding: utf-8 -*-
r2 emssr2
"""lane64 の三本めの道。W 枚の lane を numpy の配列に束ね、block を一度に走らせる。
r2 emssr3
r2 emssr4
doc: casm.lane64.sem casm.lane64.merge casm.lane64.cost casm.simd
r2 emssr5
r2 emssr6
    解釈器   vcpu.Interp     式 (2.1) の mask 付き意味。これが定義になる
r2 emssr7
    翻訳器   vcpu.run_lane   block を Python の code object へ落とす。一枚
r2 emssr8
    束ね     ここ            同じ block を走る lane を配列の一次元に並べる
r2 emssr9
r2 emssr10
命題 6.1 が束ねの根拠になる。lane の作業領域は式 (1.2) で交わらないので、
r2 emssr11
(mask, pc) の組を pc 昇順に進める観測は、lane を一本ずつ走らせた観測に一致する。
r2 emssr12
だから組を「lane の添字」で持ってよく、mask の bit 演算は要らない。
r2 emssr13
r2 emssr14
20 章 §0.4 は「大整数の欄へ W 個を詰める」形を退けている。乗算が欄をまたいで
r2 emssr15
畳み込み、LD と ST は lane ごとに番地が違うためになる。配列はその制限を受けない。
r2 emssr16
gather と scatter が LD と ST をそのまま運ぶので、頭打ちの式 (0.3) は効かない。
r2 emssr17
r2 emssr18
実測(CPython 3.12 + numpy 2.5、一 lane あたり ns)
r2 emssr19
r2 emssr20
    種      list   W=256   W=1024      倍
r2 emssr21
    ADD     37.3    3.54     1.43     26
r2 emssr22
    MUL     35.5    3.48     1.44     25
r2 emssr23
    LD 定   26.9    1.46     0.54     50      番地が定数。M の行がそのまま連続になる
r2 emssr24
    ST 定   28.9    1.56     0.53     55
r2 emssr25
    LD 変   26.9   10.21     5.87      4.6    番地がワイヤの値。gather になる
r2 emssr26
    ST 変   28.9    8.56     4.94      5.9
r2 emssr27
    比較    30.8    3.17     1.18     26
r2 emssr28
r2 emssr29
作業領域は (語数, W) に取る。(W, 語数) では定数番地が stride になり、可変番地の
r2 emssr30
gather も遅くなる(W=1024 で 5.95 対 7.80 ns)。
r2 emssr31
r2 emssr32
    重さ = 語数 x W x 8 byte。W=256 で 語数 70000 なら 143 MB
r2 emssr33
r2 emssr34
語は Z(多倍長)が仕様(§0.3)だが、配列は int64 になる。だから `overflow` が
r2 emssr35
溢れを見る。溢れたら翻訳器の道へ落とす。証とソルバーの語は在庫・金・手数・
r2 emssr36
区画の番号なので 2^62 に遠く及ばない。commonc の compile はここへ載せない。
r2 emssr37
"""
r2 emssr38
r2 emssr39
import numpy as np
r2 emssr40
r2 emssr41
import vcpu
r2 emssr42
from vcpu import (ADD, EQ, HALT, JMP, KO, LD, LT, MAT, MUL, NEG, OK, PUT, ST,
r2 emssr43
                  LANDMARKS, NAME, R_END, R_KO, R_OK, blocks)
r2 emssr44
r2 emssr45
GUARD = 1 << 62          # これを越えた語が出たら int64 の巻き戻しを疑う
r2 emssr46
r2 emssr47
r2 emssr48
class LaneVec(object):
r2 emssr49
    """W 枚の lane(式 1.1 の Σ_lane64 を W 本)。
r2 emssr50
r2 emssr51
    R[a] が landmark a の lane 方向のベクトル、M[c] が語 c の lane 方向のベクトルになる。
r2 emssr52
    どちらも第一軸が番号なので、定数番地は連続した W 語になる。
r2 emssr53
    """
r2 emssr54
r2 emssr55
    def __init__(self, width, words):
r2 emssr56
        self.W = width
r2 emssr57
        self.R = np.zeros((LANDMARKS, width), dtype=np.int64)
r2 emssr58
        self.M = np.zeros((words, width), dtype=np.int64)
r2 emssr59
        self.ix = np.arange(width, dtype=np.int64)
r2 emssr60
        self.OUT = [[] for _ in range(width)]
r2 emssr61
        self.report = [None] * width
r2 emssr62
        self.blocks = 0
r2 emssr63
r2 emssr64
    def seed(self, addr, values):
r2 emssr65
        """語 addr へ lane ごとの値を置く。put_input と違い、こちらは一語だけ動かす"""
r2 emssr66
        self.M[addr] = values
r2 emssr67
r2 emssr68
    def put_input(self, data, base):
r2 emssr69
        """18 章 §7.1 と同じ置き方(式 8.2)。全 lane へ同じ入力を配る"""
r2 emssr70
        n = min(len(data), self.M.shape[0] - base)
r2 emssr71
        self.M[0] = n
r2 emssr72
        self.M[1] = base
r2 emssr73
        self.M[base:base + n] = np.asarray(data[:n], dtype=np.int64)[:, None]
r2 emssr74
        return n
r2 emssr75
r2 emssr76
    def overflow(self):
r2 emssr77
        """int64 の巻き戻しを疑う語があるか。無ければ False"""
r2 emssr78
        return bool(np.abs(self.R).max() >= GUARD or np.abs(self.M).max() >= GUARD)
r2 emssr79
r2 emssr80
r2 emssr81
# ===== 分岐 =====
r2 emssr82
#
r2 emssr83
# 閉じの判定は lane ごとに割れる。三通りを返す。
r2 emssr84
#
r2 emssr85
#   全部が真     行き先ひとつ
r2 emssr86
#   全部が偽     行き先ひとつ
r2 emssr87
#   割れた       (真の lane の印, 真の行き先, 偽の行き先)
r2 emssr88
#
r2 emssr89
# 割れた先は run_vec が組へ足す。命題 6.1 により pc 昇順で進めれば観測は変わらない。
r2 emssr90
r2 emssr91
def _br(cond, yes, no):
r2 emssr92
    if cond.all():
r2 emssr93
        return yes
r2 emssr94
    if not cond.any():
r2 emssr95
        return no
r2 emssr96
    return (cond, yes, no)
r2 emssr97
r2 emssr98
r2 emssr99
# ===== block を numpy の文へ落とす =====
r2 emssr100
#
r2 emssr101
# 局所の値番号付けは翻訳器(vcpu._block)と同じ。違いは三つだけになる。
r2 emssr102
#
r2 emssr103
#   landmark   R[a][s]        s は組の印。全 lane なら slice(None) で view になる
r2 emssr104
#   作業領域   M[c][s]        番地が定数のとき。連続した W 語
r2 emssr105
#              M[addr, ixs]   番地がワイヤの値のとき。gather / scatter
r2 emssr106
#   即値       そのまま置く。numpy が broadcast する
r2 emssr107
r2 emssr108
def _block_vec(prog, k, body, term, stop, idx, n, out_live=None, dead=None, scan=None):
r2 emssr109
    steps = list(body) + ([] if term is None else [term])
r2 emssr110
    reg, mem, tmp = {}, {}, [0]
r2 emssr111
    out = ['def _v%d(R, M, OUT, ixs, s):' % k]
r2 emssr112
r2 emssr113
    # 定数番地の Load は M[c][s] になる。s が slice(None) のとき、これは写しではなく
r2 emssr114
    # view になる。同じ block が後で M[c][s] へ書くと、先に読んだ名前の中身が一緒に
r2 emssr115
    # 動く。翻訳器の道では M[c] が Python の int なので起きない。
r2 emssr116
    #
r2 emssr117
    #   t1 = M[A][s]        view
r2 emssr118
    #   M[A][s] = t2
r2 emssr119
    #   M[B][s] = t1 + t2   t1 は既に t2。Fibonacci が 2 の冪へ化ける
r2 emssr120
    #
r2 emssr121
    # 後ろに Store がある Load だけ写しを取る。番地がワイヤの値のときは
r2 emssr122
    # fancy indexing が写しを返すので、そのままでよい。
r2 emssr123
    last_st = -1
r2 emssr124
    for _p, _i in enumerate(body):
r2 emssr125
        if prog[_i][0] == ST:
r2 emssr126
            last_st = _p
r2 emssr127
r2 emssr128
    def get(r):
r2 emssr129
        if r not in reg:
r2 emssr130
            reg[r] = 'R[%d][s]' % r
r2 emssr131
        return reg[r]
r2 emssr132
r2 emssr133
    def lit(e):
r2 emssr134
        try:
r2 emssr135
            return int(e)
r2 emssr136
        except ValueError:
r2 emssr137
            return None
r2 emssr138
r2 emssr139
    def shifted(r, sh):
r2 emssr140
        e = get(r)
r2 emssr141
        v = lit(e)
r2 emssr142
        if sh == 0:
r2 emssr143
            return e
r2 emssr144
        if v is not None:
r2 emssr145
            return str(v << sh)
r2 emssr146
        return '(%s<<%d)' % (e, sh)
r2 emssr147
r2 emssr148
    def fresh(expr):
r2 emssr149
        tmp[0] += 1
r2 emssr150
        name = 't%d' % tmp[0]
r2 emssr151
        out.append('    %s=%s' % (name, expr))
r2 emssr152
        return name
r2 emssr153
r2 emssr154
    def fold2(x, y, f, sym):
r2 emssr155
        vx, vy = lit(x), lit(y)
r2 emssr156
        if vx is not None and vy is not None:
r2 emssr157
            return str(f(vx, vy))
r2 emssr158
        return fresh('%s%s%s' % (x, sym, y))
r2 emssr159
r2 emssr160
    for pos, i in enumerate(body):
r2 emssr161
        op, a, b, c, sb, sc, im = prog[i]
r2 emssr162
        if op == MAT:
r2 emssr163
            reg[a] = str(im)
r2 emssr164
        elif op == ADD:
r2 emssr165
            reg[a] = fold2(shifted(b, sb), shifted(c, sc), lambda x, y: x + y, '+')
r2 emssr166
        elif op == MUL:
r2 emssr167
            reg[a] = fold2(get(b), get(c), lambda x, y: x * y, '*')
r2 emssr168
        elif op == NEG:
r2 emssr169
            e = get(b)
r2 emssr170
            v = lit(e)
r2 emssr171
            reg[a] = str(-v) if v is not None else fresh('-%s' % e)
r2 emssr172
        elif op == LD:
r2 emssr173
            addr = get(b)
r2 emssr174
            v = lit(addr)
r2 emssr175
            if v is not None and v in mem:
r2 emssr176
                reg[a] = mem[v]
r2 emssr177
            else:
r2 emssr178
                if scan is not None:
r2 emssr179
                    scan.add(v)
r2 emssr180
                reg[a] = fresh(('M[%s][s].copy()' if pos < last_st else 'M[%s][s]') % addr
r2 emssr181
                               if v is not None else 'M[%s,ixs]' % addr)
r2 emssr182
                if v is not None:
r2 emssr183
                    mem[v] = reg[a]
r2 emssr184
        elif op == ST:
r2 emssr185
            addr, val = get(b), get(c)
r2 emssr186
            v = lit(addr)
r2 emssr187
            if not (dead is not None and v in dead):
r2 emssr188
                out.append('    M[%s][s]=%s' % (addr, val) if v is not None
r2 emssr189
                           else '    M[%s,ixs]=%s' % (addr, val))
r2 emssr190
            if v is not None:
r2 emssr191
                mem[v] = val
r2 emssr192
            else:
r2 emssr193
                mem.clear()
r2 emssr194
        elif op == PUT:
r2 emssr195
            # 出力は lane ごとに別の列になるので、ここだけは Python の loop になる。
r2 emssr196
            # ixs が lane の実番号を持つので、組が割れていても行き先を取り違えない
r2 emssr197
            out.append('    _o=%s' % get(b))
r2 emssr198
            out.append('    for _p in range(len(ixs)): OUT[int(ixs[_p])].append(int(_o[_p])&255)')
r2 emssr199
        else:
r2 emssr200
            raise ValueError('直線部分に置けない箱: %s' % NAME[op])
r2 emssr201
r2 emssr202
    keep = set(reg) if out_live is None else (set(reg) & out_live)
r2 emssr203
    for r in sorted(keep):
r2 emssr204
        if reg[r] != 'R[%d][s]' % r:
r2 emssr205
            out.append('    R[%d][s]=%s' % (r, reg[r]))
r2 emssr206
r2 emssr207
    def to(t):
r2 emssr208
        return idx[t] if 0 <= t < n and t in idx else R_END
r2 emssr209
r2 emssr210
    if term is None:
r2 emssr211
        out.append('    return %d' % (idx[stop] if stop < n else R_END))
r2 emssr212
    else:
r2 emssr213
        op, _a, b, c, sb, sc, im = prog[term]
r2 emssr214
        if op == OK:
r2 emssr215
            out.append('    return %d' % R_OK)
r2 emssr216
        elif op == KO:
r2 emssr217
            out.append('    return %d' % R_KO)
r2 emssr218
        elif op in (HALT,):
r2 emssr219
            out.append('    return %d' % R_END)
r2 emssr220
        elif op == JMP:
r2 emssr221
            out.append('    return %d' % to(term + im))
r2 emssr222
        else:
r2 emssr223
            x, y = shifted(b, sb), shifted(c, sc)
r2 emssr224
            vx, vy = lit(x), lit(y)
r2 emssr225
            if vx is not None and vy is not None:
r2 emssr226
                taken = (vx == vy) if op == EQ else (vx < vy)
r2 emssr227
                out.append('    return %d' % (to(term + im) if taken else to(term + 1)))
r2 emssr228
            else:
r2 emssr229
                rel = '==' if op == EQ else '<'
r2 emssr230
                out.append('    return _br(%s%s%s,%d,%d)' % (x, rel, y, to(term + im), to(term + 1)))
r2 emssr231
    return out + ['']
r2 emssr232
r2 emssr233
r2 emssr234
def translate(prog, wires=None):
r2 emssr235
    """image を numpy の block 関数の表へ落とす。引数は vcpu.translate と同じ意味になる。
r2 emssr236
r2 emssr237
    wires = (lo, hi) はワイヤの区間。他のどの block も読まない Store を落とす。
r2 emssr238
    """
r2 emssr239
    ent, idx, bl = blocks(prog)
r2 emssr240
    n = len(prog)
r2 emssr241
    live = vcpu._liveness(prog, bl, idx, n)
r2 emssr242
    dead = None
r2 emssr243
    if wires is not None:
r2 emssr244
        lo, hi = wires
r2 emssr245
        read = set()
r2 emssr246
        for k, (pc, body, term, stop) in enumerate(bl):
r2 emssr247
            _block_vec(prog, k, body, term, stop, idx, n, live[k], scan=read)
r2 emssr248
        dead = set(c for c in range(lo, hi) if c not in read)
r2 emssr249
    lines = []
r2 emssr250
    for k, (pc, body, term, stop) in enumerate(bl):
r2 emssr251
        lines += _block_vec(prog, k, body, term, stop, idx, n, live[k], dead=dead)
r2 emssr252
    lines.append('TAB = (' + ''.join('_v%d,' % k for k in range(len(bl))) + ')')
r2 emssr253
    src = chr(10).join(lines)
r2 emssr254
    ns = {'_br': _br, 'np': np}
r2 emssr255
    exec(compile(src, '<lane64-vec>', 'exec'), ns)
r2 emssr256
    return src, ns['TAB']
r2 emssr257
r2 emssr258
r2 emssr259
# ===== 組を進める(命題 6.1)=====
r2 emssr260
#
r2 emssr261
# 組は (lane の印, block 番号) の対で持つ。印は slice(None) か添字の配列になる。
r2 emssr262
# slice(None) のときは R も M も view を返すので、fancy indexing の写しが出ない。
r2 emssr263
# 組が割れたら添字の配列になり、同じ block 番号の組は一つへ合わせる。
r2 emssr264
#
r2 emssr265
# 「pc 昇順」は block 番号の昇順で足りる。block の入口は pc の昇順に並ぶので
r2 emssr266
# (vcpu.entries が sorted を返す)、番号の順序が pc の順序に一致する。
r2 emssr267
r2 emssr268
_ALL = slice(None)
r2 emssr269
r2 emssr270
r2 emssr271
def _join(groups, fresh):
r2 emssr272
    """同じ block 番号の組を合わせる(式 3.3 の reconvergence)"""
r2 emssr273
    for sel, b in fresh:
r2 emssr274
        for k, (s2, b2) in enumerate(groups):
r2 emssr275
            if b2 == b:
r2 emssr276
                if s2 is _ALL or sel is _ALL:
r2 emssr277
                    groups[k] = (_ALL, b)
r2 emssr278
                else:
r2 emssr279
                    groups[k] = (np.union1d(s2, sel), b)
r2 emssr280
                break
r2 emssr281
        else:
r2 emssr282
            groups.append((sel, b))
r2 emssr283
    return groups
r2 emssr284
r2 emssr285
r2 emssr286
def run(tab, lv, budget=1 << 32):
r2 emssr287
    """W 枚を束ねて走らせる。返りは lane ごとの報告の列。
r2 emssr288
r2 emssr289
    報告は翻訳器と同じ値を使う。0 が tu、42 が te、None が停止になる。
r2 emssr290
    """
r2 emssr291
    R, M, OUT, ix = lv.R, lv.M, lv.OUT, lv.ix
r2 emssr292
    groups = [(_ALL, 0)]
r2 emssr293
    steps = 0
r2 emssr294
    while groups:
r2 emssr295
        steps += 1
r2 emssr296
        if steps > budget:
r2 emssr297
            raise RuntimeError('組の歩数上限 %d を越えた' % budget)
r2 emssr298
        # 最も小さい block 番号の組を取る(命題 6.1)
r2 emssr299
        k = 0 if len(groups) == 1 else min(range(len(groups)), key=lambda i: groups[i][1])
r2 emssr300
        sel, b = groups.pop(k)
r2 emssr301
        ixs = ix if sel is _ALL else ix[sel]
r2 emssr302
        r = tab[b](R, M, OUT, ixs, sel)
r2 emssr303
        if isinstance(r, tuple):
r2 emssr304
            cond, yes, no = r
r2 emssr305
            base = ix if sel is _ALL else ix[sel]
r2 emssr306
            groups = _join(groups, [(base[cond], yes), (base[~cond], no)])
r2 emssr307
            continue
r2 emssr308
        if r >= 0:
r2 emssr309
            groups = _join(groups, [(sel, r)])
r2 emssr310
            continue
r2 emssr311
        rep = 0 if r == R_OK else (42 if r == R_KO else None)
r2 emssr312
        if sel is _ALL:
r2 emssr313
            lv.report = [rep] * lv.W
r2 emssr314
        else:
r2 emssr315
            for l in sel:
r2 emssr316
                lv.report[int(l)] = rep
r2 emssr317
    lv.blocks = steps
r2 emssr318
    return lv.report
r2 emssr319
r2 emssr320
r2 emssr321
# ===== 突き合わせ(命題 6.1 の検査)=====
r2 emssr322
r2 emssr323
def agrees(image, seeds, words, need=None, watch=()):
r2 emssr324
    """束ねの道と翻訳器の道が同じ観測を返すか。
r2 emssr325
r2 emssr326
    命題 6.1 の最後が「二つの観測が一致することが、この package の検査の一つになる」
r2 emssr327
    と言う。seeds は lane ごとの {番地: 値}。返りは (合うか, 束ねの報告, 翻訳器の報告)。
r2 emssr328
    """
r2 emssr329
    prog = vcpu.decode(image)
r2 emssr330
    W = len(seeds)
r2 emssr331
    lv = LaneVec(W, words)
r2 emssr332
    for l, s in enumerate(seeds):
r2 emssr333
        for a, v in s.items():
r2 emssr334
            lv.M[a, l] = v
r2 emssr335
    _src, tabv = translate(prog, wires=(64, need) if need else None)
r2 emssr336
    rep_v = list(run(tabv, lv))
r2 emssr337
    val_v = [[int(lv.M[a, l]) for a in watch] for l in range(W)]
r2 emssr338
r2 emssr339
    _src2, tab = vcpu.translate(prog, wires=(64, need) if need else None)
r2 emssr340
    rep_s, val_s = [], []
r2 emssr341
    for s in seeds:
r2 emssr342
        lane = vcpu.Lane(words)
r2 emssr343
        for a, v in s.items():
r2 emssr344
            lane.M[a] = v
r2 emssr345
        rep_s.append(vcpu.run_lane(tab, lane))
r2 emssr346
        val_s.append([lane.M[a] for a in watch])
r2 emssr347
    ok = (rep_v == rep_s and val_v == val_s and not lv.overflow())
r2 emssr348
    return ok, rep_v, rep_s