cultmath glsl build: spvedit.py, the one SPIR-V edit with by-value parameters
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
da082e7e7c
commit
b10f4530d0
1 changed files with 189 additions and 0 deletions
189
packages/cultmath/tools/glsl/spvedit.py
Normal file
189
packages/cultmath/tools/glsl/spvedit.py
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
#!/usr/bin/env python3
|
||||
"""The one instruction-level SPIR-V edit of the GLSL build. Binary, never text.
|
||||
|
||||
usage: spvedit.py <in.spv> <out.spv> <wrappers.tsv>
|
||||
1. Keeps only the capabilities Shader and Linkage and drops every OpExtension.
|
||||
2. Deletes each wrapper function with its OpName and decorations, moves its LinkageAttributes Export to its callee, and
|
||||
sets the callee's OpName to the source name. Refuses a wrapper that is not exactly one OpFunctionCall.
|
||||
3. By-value lowering: dxc -Od passes every parameter as a Function pointer and the caller copies in, so a callee that
|
||||
assigns its parameter comes out of SPIRV-Cross as `inout`. Each Function-pointer OpFunctionParameter becomes a value
|
||||
parameter with a new id; the old id stays as an entry-block OpVariable stored from it (OpName copied to the new id);
|
||||
OpTypeFunction is reused if the signature exists, else added; each OpFunctionCall loads the pointer it passed.
|
||||
The header bound is last id + 1."""
|
||||
import struct, sys
|
||||
|
||||
OP_NAME, OP_MEMBER_NAME, OP_CAPABILITY, OP_EXTENSION, OP_DECORATE = 5, 6, 17, 10, 71
|
||||
OP_TYPE_POINTER, OP_TYPE_FUNCTION, OP_FUNCTION, OP_PARAMETER, OP_FUNCTION_END = 32, 33, 54, 55, 56
|
||||
OP_CALL, OP_LABEL, OP_VARIABLE, OP_LOAD, OP_STORE = 57, 248, 59, 61, 62
|
||||
CAP_SHADER, CAP_LINKAGE, DECORATION_LINKAGE, LINKAGE_EXPORT, FUNCTION_STORAGE = 1, 5, 41, 41, 7
|
||||
TERMINATORS = (8, 317, 62, 246, 247, 249, 250, 253, 254, 252)
|
||||
|
||||
|
||||
def load(path):
|
||||
raw = open(path, 'rb').read()
|
||||
words = list(struct.unpack('<%dI' % (len(raw) // 4), raw))
|
||||
ins, i = [], 5
|
||||
while i < len(words):
|
||||
n = words[i] >> 16
|
||||
ins.append(words[i:i + n])
|
||||
i += n
|
||||
return words[:5], ins
|
||||
|
||||
|
||||
def op(x): return x[0] & 0xffff
|
||||
def mk(o, *ws): return [((len(ws) + 1) << 16) | o, *ws]
|
||||
|
||||
|
||||
def decode(ws):
|
||||
b = struct.pack('<%dI' % len(ws), *ws)
|
||||
return b[:b.index(0)].decode()
|
||||
|
||||
|
||||
def encode(s):
|
||||
b = s.encode() + b'\0'
|
||||
b += b'\0' * (-len(b) % 4)
|
||||
return list(struct.unpack('<%dI' % (len(b) // 4), b))
|
||||
|
||||
|
||||
def functions(ins):
|
||||
"""function id -> [first index, last index, callee ids, ids defined inside]"""
|
||||
fs, cur = {}, None
|
||||
for k, x in enumerate(ins):
|
||||
o = op(x)
|
||||
if o == OP_FUNCTION:
|
||||
cur = x[2]
|
||||
fs[cur] = [k, None, [], set()]
|
||||
if cur is None:
|
||||
continue
|
||||
if o == OP_CALL:
|
||||
fs[cur][2].append(x[3])
|
||||
if o == OP_LABEL:
|
||||
fs[cur][3].add(x[1])
|
||||
elif o not in TERMINATORS and len(x) > 2:
|
||||
fs[cur][3].add(x[2])
|
||||
if o == OP_FUNCTION_END:
|
||||
fs[cur][1] = k
|
||||
cur = None
|
||||
return fs
|
||||
|
||||
|
||||
def export_edit(h, ins, rows):
|
||||
names = {x[1]: decode(x[2:]) for x in ins if op(x) == OP_NAME}
|
||||
fs = functions(ins)
|
||||
by_name = {v: k for k, v in names.items() if k in fs}
|
||||
drop_k, drop_ids, set_name, retarget = set(), set(), {}, {}
|
||||
for wn, name, _, _ in rows:
|
||||
fid = by_name.get(wn)
|
||||
callees = fs[fid][2] if fid else None
|
||||
if not callees or len(callees) != 1:
|
||||
sys.exit('refuse: wrapper %s is not exactly one OpFunctionCall' % wn)
|
||||
a, b, _, ids = fs[fid]
|
||||
drop_k.update(range(a, b + 1))
|
||||
drop_ids |= ids | {fid}
|
||||
set_name[callees[0]] = name
|
||||
retarget[fid] = callees[0]
|
||||
out, named = [], set()
|
||||
for k, x in enumerate(ins):
|
||||
if k in drop_k:
|
||||
continue
|
||||
o = op(x)
|
||||
if o == OP_CAPABILITY and x[1] not in (CAP_SHADER, CAP_LINKAGE):
|
||||
continue
|
||||
if o == OP_EXTENSION:
|
||||
continue
|
||||
if o == OP_NAME and x[1] in set_name:
|
||||
x = mk(OP_NAME, x[1], *encode(set_name[x[1]]))
|
||||
named.add(x[1])
|
||||
elif o in (OP_NAME, OP_MEMBER_NAME) and x[1] in drop_ids:
|
||||
continue
|
||||
if o == OP_DECORATE and x[1] in drop_ids:
|
||||
if x[1] in retarget and x[2] == DECORATION_LINKAGE:
|
||||
x = [x[0], retarget[x[1]]] + x[2:]
|
||||
else:
|
||||
continue
|
||||
out.append(x)
|
||||
for fid, name in set_name.items():
|
||||
if fid not in named:
|
||||
out.insert(next(i for i, x in enumerate(out) if op(x) == OP_NAME), mk(OP_NAME, fid, *encode(name)))
|
||||
return out, len(rows)
|
||||
|
||||
|
||||
def by_value(h, ins):
|
||||
bound = h[3]
|
||||
ptr = {x[1]: (x[2], x[3]) for x in ins if op(x) == OP_TYPE_POINTER} # id -> (storage, pointee)
|
||||
ftype = {x[1]: x for x in ins if op(x) == OP_TYPE_FUNCTION}
|
||||
fnparams, fntype, cur = {}, {}, None
|
||||
for x in ins: # per function: which parameters are Function pointers
|
||||
if op(x) == OP_FUNCTION:
|
||||
cur = x[2]
|
||||
fnparams[cur], fntype[cur] = [], x[4]
|
||||
elif op(x) == OP_PARAMETER and cur is not None:
|
||||
t = x[1]
|
||||
conv = t in ptr and ptr[t][0] == FUNCTION_STORAGE
|
||||
fnparams[cur].append((x[2], t, ptr[t][1] if conv else None, bound if conv else None))
|
||||
bound += conv
|
||||
newid = {p[0]: p[3] for ps in fnparams.values() for p in ps if p[2]}
|
||||
newtype, typeins = {}, []
|
||||
sig = {tuple(x[2:]): x[1] for x in ins if op(x) == OP_TYPE_FUNCTION}
|
||||
for f, ps in fnparams.items():
|
||||
if any(p[2] for p in ps):
|
||||
key = (ftype[fntype[f]][2], *[p[2] if p[2] else p[1] for p in ps])
|
||||
if key not in sig:
|
||||
bound += 1
|
||||
sig[key] = bound
|
||||
typeins.append(mk(OP_TYPE_FUNCTION, bound, *key))
|
||||
newtype[f] = sig[key]
|
||||
out, cur, pend, need_stores, first, pc = [], None, [], False, True, 0
|
||||
for x in ins:
|
||||
o = op(x)
|
||||
if o == OP_FUNCTION and first:
|
||||
out += typeins
|
||||
first = False
|
||||
if need_stores and o != OP_VARIABLE:
|
||||
out += [mk(OP_STORE, p, q) for p, _, q in pend]
|
||||
pend, need_stores = [], False
|
||||
if o == OP_FUNCTION:
|
||||
cur, pc = x[2], 0
|
||||
if cur in newtype:
|
||||
x = x[:4] + [newtype[cur]] + x[5:]
|
||||
elif o == OP_PARAMETER:
|
||||
_, t, pointee, q = fnparams[cur][pc]
|
||||
pc += 1
|
||||
if pointee:
|
||||
out.append(mk(OP_PARAMETER, pointee, q))
|
||||
pend.append((x[2], t, q))
|
||||
continue
|
||||
elif o == OP_LABEL and pend:
|
||||
out.append(x)
|
||||
out += [mk(OP_VARIABLE, t, p, FUNCTION_STORAGE) for p, t, _ in pend]
|
||||
need_stores = True
|
||||
continue
|
||||
elif o == OP_CALL:
|
||||
args = []
|
||||
for a, (_, _, pointee, _) in zip(x[4:], fnparams[x[3]]):
|
||||
if pointee:
|
||||
bound += 1
|
||||
out.append(mk(OP_LOAD, pointee, bound, a))
|
||||
args.append(bound)
|
||||
else:
|
||||
args.append(a)
|
||||
x = mk(OP_CALL, *x[1:4], *args)
|
||||
out.append(x)
|
||||
if o == OP_NAME and x[1] in newid:
|
||||
out.append([x[0], newid[x[1]]] + x[2:])
|
||||
h[3] = bound + 1
|
||||
return out, len(newtype), len(newid)
|
||||
|
||||
|
||||
def main(src, dst, tsv):
|
||||
h, ins = load(src)
|
||||
rows = [l.split('\t') for l in open(tsv).read().splitlines() if l]
|
||||
ins, wrappers = export_edit(h, ins, rows)
|
||||
ins, changed, params = by_value(h, ins)
|
||||
words = h + [v for x in ins for v in x]
|
||||
open(dst, 'wb').write(struct.pack('<%dI' % len(words), *words))
|
||||
print('spvedit: %d wrappers dropped; by-value: %d functions changed, %d parameters' % (wrappers, changed, params))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(*sys.argv[1:4])
|
||||
Loading…
Reference in a new issue