diff options
Diffstat (limited to 'tools/asm.py')
-rwxr-xr-x | tools/asm.py | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/tools/asm.py b/tools/asm.py new file mode 100755 index 0000000..4794b98 --- /dev/null +++ b/tools/asm.py @@ -0,0 +1,324 @@ +#!/usr/bin/python +''' +Shader assembler. + +Usage: asm.py ../rnndb/isa.xml in.asm out.bin +''' +# Copyright (c) 2012-2013 Wladimir J. van der Laan +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sub license, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the +# next paragraph) shall be included in all copies or substantial portions +# of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +from __future__ import print_function, division, unicode_literals +import argparse,struct +import sys +from binascii import b2a_hex +import re + +from etnaviv.parse_rng import parse_rng_file, format_path, BitSet, Domain +from etnaviv.asm_common import DstOperand, DstOperandAReg, SrcOperand, TexOperand, AddrOperand, Instruction, AMODES, COMPS +from etnaviv.asm_common import disassemble, format_instruction + +reg_re = re.compile('^(t|u|a|tex)(\d+)(\[.*?\])?(\.[xyzw]{1,4})?$') +label_re = re.compile('^[0-9a-zA-Z\-\_]+$') + +def parse_amode(amode): + if not amode: + return 0 + return AMODES.index(amode[1:-1]) + +def parse_comps(comps): + if not comps: + return 15 + return ((('x' in comps)<<0)|(('y' in comps)<<1)|(('z' in comps)<<2)|(('w' in comps)<<3)) + +def parse_swiz(swiz): + if not swiz: + return 0xe4 + rv = 0 + for idx in xrange(4): + if idx < len(swiz): + comp = COMPS.index(swiz[idx+1]) + rv |= comp << (idx * 2) + return rv + +def assemble(isa, inst, warnings): + fields = {} + fields['OPCODE'] = inst.op + fields['COND'] = inst.cond + fields['SAT'] = inst.sat + + if isinstance(inst.dst, DstOperandAReg): + # XXX validate that this instruction accepts + # address destination arguments + fields['DST_REG'] = inst.dst.reg + fields['DST_COMPS'] = inst.dst.comps + elif isinstance(inst.dst, DstOperand): + fields['DST_USE'] = inst.dst.use + fields['DST_AMODE'] = inst.dst.amode + fields['DST_REG'] = inst.dst.reg + fields['DST_COMPS'] = inst.dst.comps + elif inst.dst is None: + fields['DST_USE'] = 0 + else: + warnings.append('Invalid destination argument') + + if inst.tex is not None: + fields['TEX_ID'] = inst.tex.id + fields['TEX_AMODE'] = inst.tex.amode + fields['TEX_SWIZ'] = inst.tex.swiz + + if inst.addr is not None: + fields['SRC2_IMM'] = inst.addr.addr + + for (idx, src) in enumerate(inst.src): + if src is not None: + fields['SRC%i_USE' % idx] = src.use + fields['SRC%i_REG' % idx] = src.reg + fields['SRC%i_SWIZ' % idx] = src.swiz + fields['SRC%i_NEG' % idx] = src.neg + fields['SRC%i_ABS' % idx] = src.abs + fields['SRC%i_AMODE' % idx] = src.amode + fields['SRC%i_RGROUP' % idx] = src.rgroup + + # XXX check for colliding fields + domain = isa.lookup_domain('VIV_ISA') + rv = [0,0,0,0] + for word in [0,1,2,3]: + mask = 0 + bitset = domain.lookup_address(word*4)[-1][0].type + for field in bitset.bitfields: + if field.name in fields: + try: + rv[word] |= field.fill(fields[field.name]) + del fields[field.name] + except ValueError,e: + warnings.append(str(e)) + for field in fields.iterkeys(): # warn if fields are not used, that's probably a typo + warnings.append('Field %s not used' % field) + return rv + +class Assembler(object): + ''' + Instruction assembler context. + ''' + labels = {} + linenr = 0 + instructions = None + source = None + def __init__(self, isa): + self.isa = isa + self.errors = [] + self.instructions = [] + self.source = [] + + def parse(self, line): + # remove comment + self.source.append(line) + self.linenr += 1 + (line, _, _) = line.partition(';') + + (label, _, line) = line.rpartition(':') + if label: + label = label.strip() + if not label_re.match(label): + self.errors.append((self.linenr, 'Invalid label: %s' % label)) + self.labels[label] = len(self.instructions) + + (inst, _, operands) = line.strip().partition(' ') + + inst = inst.split('.') + if not inst[0]: # empty line + return None + + try: + op = self.isa.types['INST_OPCODE'].values_by_name[inst[0]].value + except KeyError: + self.errors.append((self.linenr, 'Unknown instruction %s' % inst[0])) + return None + + cond = 0 + sat = False + conditions = self.isa.types['INST_CONDITION'].values_by_name + for atom in inst[1:]: + if atom in conditions: + cond = conditions[atom].value + elif atom == 'SAT': + sat = True + else: + self.errors.append((self.linenr, 'Unknown atom %s' % atom)) + return None + + operands = operands.split(',') + src = [] + dst = None + tex = None + addr = None + for idx,operand in enumerate(operands): + operand = operand.strip() + neg = False + abs = False + if operand.startswith('|'): + if not operand.endswith('|'): + self.errors.append((self.linenr, 'Unterminated |')) + abs = True + operand = operand[1:-1] + if operand.startswith('-'): + neg = True + operand = operand[1:] + + # check kind of operand + # (t|u|a)XXX[.xyzw] (address)register + match_reg = reg_re.match(operand) + if match_reg: + (regtype, regid, amode, swiz) = match_reg.groups() + regid = int(regid) + try: + amode = parse_amode(amode) + except LookupError: + self.errors.append((self.linenr, 'Unknown amode %s' % amode)) + amode = 0 + if idx == 0: # destination operand + comps = parse_comps(swiz) + if regtype == 't': + dst = DstOperand(use=1, amode=amode, reg=regid, comps=comps) + elif regtype == 'a': + dst = DstOperandAReg(reg=regid, comps=comps) + else: + self.errors.append((self.linenr, 'Cannot have texture or uniform as destination argument')) + else: # source operand + try: + swiz = parse_swiz(swiz) + except LookupError: + self.errors.append((self.linenr, 'Unknown swizzle %s' % swiz)) + swiz = 0 + if regtype == 't' or regtype == 'u': + rgroup = 0 + if regtype == 'u': + if regid < 128: + rgroup = 2 + else: + rgroup = 3 + regid -= 128 + src.append(SrcOperand(use=1, reg=regid, swiz=swiz, neg=neg, abs=abs, amode=amode, rgroup=rgroup)) + elif regtype == 'a': + src.append(DstOperandAReg(reg=regid, comps=comps)) + elif regtype == 'tex': + tex = TexOperand(id=regid, amode=amode, swiz=swiz) + else: + self.errors.append((self.linenr, 'Unparseable register type %s' % regtype)) + arg_obj = None + elif operand == 'void': + #print('void') + if idx == 0: # destination operand + dst = None + else: + src.append(None) + elif label_re.match(operand): + #print('label ', operand) + addr = AddrOperand(addr = operand) # will resolve labels later + else: + self.errors.append((self.linenr, 'Unknown operand ' + operand)) + inst_out = Instruction(op=op, + cond=cond,sat=sat, + tex=tex,dst=dst,src=src,addr=addr,unknowns={},linenr=self.linenr) + self.instructions.append(inst_out) + return inst_out + + def generate_code(self): + rv = [] + for inst in self.instructions: + warnings = [] + if inst.addr is not None: # fill in labels + try: + addr = AddrOperand(self.labels[inst.addr.addr]) + except LookupError: + self.errors.append((inst.linenr, 'Unknown label ' + inst.addr.addr)) + inst = inst._replace(addr=addr) + inst_out = assemble(self.isa, inst, warnings) + rv.append(inst_out) + + dis_i = disassemble(self.isa, inst_out, warnings) + if not compare_inst(inst, dis_i, warnings): + # Assembly did not match disassembly, print details + warnings.append('%08x %08x %08x %08x %s' % ( + inst_out[0], inst_out[1], inst_out[2], inst_out[3], format_instruction(self.isa, dis_i))) + warnings.append(' orig : %s' % (self.source[inst.linenr-1])) + + for warning in warnings: + self.errors.append((inst.linenr, warning)) + return rv + +def compare_inst(a,b,warnings): + match = True + for attr in ['op', 'cond', 'sat', 'tex', 'dst', 'src', 'addr']: + if getattr(a, attr) != getattr(b, attr): + warnings.append('Assembly/disassembly mismatch: %s %s %s' % (attr, getattr(a, attr), getattr(b, attr))) + match = False + return match + +def parse_arguments(): + parser = argparse.ArgumentParser(description='Disassemble shader') + parser.add_argument('isa_file', metavar='ISAFILE', type=str, + help='Shader ISA definition file (rules-ng-ng)') + parser.add_argument('input', metavar='INFILE', type=str, + help='Shader assembly file') + #parser.add_argument('output', metavar='OUTFILE', type=str, + # help='Binary shader file') + #parser.add_argument('-a', dest='addr', + # default=False, action='store_const', const=True, + # help='Show address data with instructions') + parser.add_argument('-o', dest='bin_out', type=str, + help='Write binary shader to output file') + return parser.parse_args() + +def main(): + args = parse_arguments() + out = sys.stdout + isa = parse_rng_file(args.isa_file) + + with open(args.input, 'rb') as f: + asm = Assembler(isa) + errors = [] + for linenr, line in enumerate(f): + line = line.rstrip('\n') + asm.parse(line) + + if not asm.errors: + code = asm.generate_code() + else: + code = None + + for line, error in asm.errors: + print('Line %i: %s' % (line, error)) + + if code is not None: + if args.bin_out is not None: + with open(args.bin_out, 'wb') as f: + for inst in code: + f.write(struct.pack(b'<IIII', *inst)) + else: # no binary output, print as ascii + for inst in code: + print('0x%08x,0x%08x,0x%08x,0x%08x,' % tuple(inst)) + + + +if __name__ == '__main__': + main() + + |