#!/usr/bin/env python3
"""Minimal SPIR-V scanner: lists OpEntryPoint interface ids, BuiltIn FragCoord
decorations, and Input-storage OpVariable instructions in a SPIR-V binary.

Used to demonstrate that ANGLE's dither-emulation transform produces TWO
distinct Input variables both decorated BuiltIn FragCoord, both listed in
OpEntryPoint -- a violation of VUID-StandaloneSpirv-OpEntryPoint-08721.
"""
import struct
import sys

OP_ENTRY_POINT = 15
OP_VARIABLE    = 59
OP_DECORATE    = 71
DECORATION_BUILTIN = 11
BUILTIN_FRAGCOORD  = 15
STORAGE_CLASS_INPUT = 1


def words(path):
    with open(path, 'rb') as f:
        data = f.read()
    return list(struct.unpack(f'<{len(data)//4}I', data))


def main(path):
    w = words(path)
    assert w[0] == 0x07230203, 'not a SPIR-V binary'
    print(f'; SPIR-V module, bound = {w[3]}')
    print()

    entry_iface = []
    fragcoord_ids = []
    input_vars = {}

    i = 5
    while i < len(w):
        word = w[i]
        wc = word >> 16
        op = word & 0xFFFF
        if wc == 0:
            break
        ops = w[i:i + wc]

        if op == OP_ENTRY_POINT:
            # [op, exec_model, entry_id, name..., iface_ids...]
            # name is nul-terminated string in 32-bit words starting at ops[3]
            j = 3
            name_bytes = b''
            while j < wc:
                chunk = struct.pack('<I', ops[j])
                name_bytes += chunk
                j += 1
                if 0 in chunk:
                    break
            entry_iface = ops[j:]
            iface_str = ' '.join(f'%{x}' for x in entry_iface)
            print(f'OpEntryPoint Fragment %{ops[2]} "main" {iface_str}')

        elif op == OP_DECORATE and wc == 4 and ops[2] == DECORATION_BUILTIN \
                and ops[3] == BUILTIN_FRAGCOORD:
            fragcoord_ids.append(ops[1])
            print(f'OpDecorate %{ops[1]} BuiltIn FragCoord')

        elif op == OP_VARIABLE and ops[3] == STORAGE_CLASS_INPUT:
            # [op, result_type, result_id, storage_class, (init)]
            input_vars[ops[2]] = ops[1]

        i += wc

    print()
    for vid in fragcoord_ids:
        tp = input_vars.get(vid, '?')
        in_ep = 'YES' if vid in entry_iface else 'no'
        print(f'%{vid} = OpVariable %{tp} Input    ; BuiltIn FragCoord, '
              f'in OpEntryPoint interface: {in_ep}')

    print()
    if len(fragcoord_ids) > 1:
        print(f'==> {len(fragcoord_ids)} Input variables decorated BuiltIn '
              f'FragCoord: {", ".join("%"+str(x) for x in fragcoord_ids)}')
        print('==> VIOLATES VUID-StandaloneSpirv-OpEntryPoint-08721')
        return 1
    print('OK: exactly one BuiltIn FragCoord')
    return 0


if __name__ == '__main__':
    sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else 'poc-transformed.spv'))
