summaryrefslogtreecommitdiff
path: root/tools/asm.py
blob: 9f815b83a0ec969bdd692976bcd5eb7cc5c37f7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#!/usr/bin/python
'''
Shader assembler.

Usage: asm.py --isa-file ../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.util import rnndb_path
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, RGROUPS
from etnaviv.asm_common import disassemble, format_instruction

reg_re = re.compile('^(i|t|u|a|tex|\?4\?|\?5\?|\?6\?|\?7\?)(\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
    swiz = swiz[1:] # drop .
    rv = 0
    for idx in xrange(4):
        if idx < len(swiz):
            comp = COMPS.index(swiz[idx])
        rv |= comp << (idx * 2)
    return rv

def assemble(isa, inst, warnings):
    fields = {}
    fields['OPCODE'] = inst.op & 0x3F
    fields['OPCODE_BIT6'] = (inst.op >> 6) & 0x01
    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('-'):
                neg = True
                operand = operand[1:]
            if operand.startswith('|'):
                if not operand.endswith('|'):
                    self.errors.append((self.linenr, 'Unterminated |'))
                abs = True
                operand = operand[1:-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, 'Unparseable swizzle %s' % swiz))
                        swiz = 0
                    if regtype in RGROUPS: # register group
                        if regtype == 'u':
                            if regid < 128:
                                rgroup = 2
                            else:
                                rgroup = 3
                                regid -= 128
                        else:
                            rgroup = RGROUPS.index(regtype)
                        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, 'Unparseable operand ' + operand))

        num_operands = 1 + len(src) + (addr is not None)
        if num_operands != 4:
            self.errors.append((self.linenr, 'Invalid number of operands (%i)' % num_operands))
        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))
                    addr = AddrOperand(0) # dummy
                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))
        if self.errors:
            return None
        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)',
            default=rnndb_path('isa.xml'))
    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))
        else:
            exit(1)



if __name__ == '__main__':
    main()