• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#encoding=utf-8
2
3# Copyright (C) 2021 Collabora, Ltd.
4#
5# Permission is hereby granted, free of charge, to any person obtaining a
6# copy of this software and associated documentation files (the "Software"),
7# to deal in the Software without restriction, including without limitation
8# the rights to use, copy, modify, merge, publish, distribute, sublicense,
9# and/or sell copies of the Software, and to permit persons to whom the
10# Software is furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice (including the next
13# paragraph) shall be included in all copies or substantial portions of the
14# Software.
15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22# IN THE SOFTWARE.
23
24import argparse
25import sys
26import struct
27from valhall import instructions, enums, immediates, typesize
28
29LINE = ''
30
31class ParseError(Exception):
32    def __init__(self, error):
33        self.error = error
34
35class FAUState:
36    def __init__(self, mode):
37        self.mode = mode
38        self.uniform_slot = None
39        self.special = None
40        self.buffer = set()
41
42    def push(self, s):
43        self.buffer.add(s)
44        die_if(len(self.buffer) > 2, "Overflowed FAU buffer")
45
46    def push_special(self, s):
47        die_if(self.special is not None and self.special != s,
48                'Multiple special immediates')
49        self.special = s
50        self.push(s)
51
52    def descriptor(self, s):
53        die_if(self.mode != 'none', f'Expected no modifier with {s}')
54        self.push_special(s)
55
56    def uniform(self, v):
57        slot = v >> 1
58
59        die_if(self.mode != 'none',
60                'Expected uniform with default immediate mode')
61        die_if(self.uniform_slot is not None and self.uniform_slot != slot,
62                'Overflowed uniform slots')
63        self.uniform_slot = slot
64        self.push(f'uniform{v}')
65
66    def id(self, s):
67        die_if(self.mode != 'id',
68                'Expected .id modifier with thread storage pointer')
69
70        self.push_special(f'id{s}')
71
72    def ts(self, s):
73        die_if(self.mode != 'ts',
74                'Expected .ts modifier with thread pointer')
75        self.push_special(f'ts{s}')
76
77    def constant(self, cons):
78        self.push(cons)
79
80# When running standalone, exit with the error since we're dealing with a
81# human. Otherwise raise a Python exception so the test harness can handle it.
82def die(s):
83    if __name__ == "__main__":
84        print(LINE)
85        print(s)
86        sys.exit(1)
87    else:
88        raise ParseError(s)
89
90def die_if(cond, s):
91    if cond:
92        die(s)
93
94def parse_int(s, minimum, maximum):
95    try:
96        number = int(s, base = 0)
97    except ValueError:
98        die(f"Expected number {s}")
99
100    if number > maximum or number < minimum:
101        die(f"Range error on {s}")
102
103    return number
104
105def encode_source(op, fau):
106    if op == 'atest_datum':
107        fau.descriptor(op)
108        return 0x2A | 0xC0
109    elif op.startswith('blend_descriptor_'):
110        fau.descriptor(op)
111        fin = op[len('blend_descriptor_'):]
112        die_if(len(fin) != 3, 'Bad syntax')
113        die_if(fin[1] != '_', 'Bad syntax')
114        die_if(fin[2] not in ['x', 'y'], 'Bad component')
115
116        rt = parse_int(fin[0], 0, 7)
117        hi = 1 if (fin[2] == 'y') else 0
118        return (0x30 | (2*rt) + hi) | 0xC0
119    elif op[0] == '`':
120        die_if(op[1] != 'r', f"Expected register after discard {op}")
121        return parse_int(op[2:], 0, 63) | 0x40
122    elif op[0] == 'r':
123        return parse_int(op[1:], 0, 63)
124    elif op[0] == 'u':
125        val = parse_int(op[1:], 0, 63)
126        fau.uniform(val)
127        return val | 0x80
128    elif op[0] == 'i':
129        return int(op[3:]) | 0xC0
130    elif op in enums['thread_storage_pointers'].bare_values:
131        fau.ts(op)
132        idx = 32 + enums['thread_storage_pointers'].bare_values.index(op)
133        return idx | 0xC0
134    elif op in enums['thread_identification'].bare_values:
135        fau.id(op)
136        idx = 32 + enums['thread_identification'].bare_values.index(op)
137        return idx | 0xC0
138    elif op.startswith('0x'):
139        try:
140            val = int(op, base=0)
141        except ValueError:
142            die('Expected value')
143
144        die_if(val not in immediates, 'Unexpected immediate value')
145        fau.constant(val)
146        return immediates.index(val) | 0xC0
147    else:
148        die('Invalid operand')
149
150def encode_dest(op):
151    die_if(op[0] != 'r', f"Expected register destination {op}")
152
153    parts = op.split(".")
154    reg = parts[0]
155
156    # Default to writing in full
157    wrmask = 0x3
158
159    if len(parts) > 1:
160        WMASKS = ["h0", "h1"]
161        die_if(len(parts) > 2, "Too many modifiers")
162        mask = parts[1];
163        die_if(mask not in WMASKS, "Expected a write mask")
164        wrmask = 1 << WMASKS.index(mask)
165
166    return parse_int(reg[1:], 0, 63) | (wrmask << 6)
167
168def parse_asm(line):
169    global LINE
170    LINE = line # For better errors
171    encoded = 0
172
173    # Figure out mnemonic
174    head = line.split(" ")[0]
175    opts = [ins for ins in instructions if head.startswith(ins.name)]
176    opts = sorted(opts, key=lambda x: len(x.name), reverse=True)
177
178    if len(opts) == 0:
179        die(f"No known mnemonic for {head}")
180
181    if len(opts) > 1 and len(opts[0].name) == len(opts[1].name):
182        print(f"Ambiguous mnemonic for {head}")
183        print(f"Options:")
184        for ins in opts:
185            print(f"  {ins}")
186        sys.exit(1)
187
188    ins = opts[0]
189
190    # Split off modifiers
191    if len(head) > len(ins.name) and head[len(ins.name)] != '.':
192        die(f"Expected . after instruction in {head}")
193
194    mods = head[len(ins.name) + 1:].split(".")
195    modifier_map = {}
196    immediate_mode = 'none'
197
198    for mod in mods:
199        if mod in enums['immediate_mode'].bare_values:
200            die_if(immediate_mode != 'none', 'Multiple immediate modes specified')
201            immediate_mode = mod
202
203    tail = line[(len(head) + 1):]
204    operands = [x.strip() for x in tail.split(",") if len(x.strip()) > 0]
205    expected_op_count = len(ins.srcs) + len(ins.dests) + len(ins.immediates) + len(ins.staging)
206    if len(operands) != expected_op_count:
207        die(f"Wrong number of operands in {line}, expected {expected_op_count}, got {len(operands)} {operands}")
208
209    # Encode each operand
210    for i, (op, sr) in enumerate(zip(operands, ins.staging)):
211        die_if(op[0] != '@', f'Expected staging register, got {op}')
212        parts = op[1:].split(':')
213
214        die_if(any([x[0] != 'r' for x in parts]), f'Expected registers, got {op}')
215        regs = [parse_int(x[1:], 0, 63) for x in parts]
216
217        sr_count = len(regs)
218        die_if(sr_count < 1, f'Expected staging register, got {op}')
219        die_if(sr_count > 7, f'Too many staging registers {sr_count}')
220
221        base = regs[0]
222        die_if(any([reg != (base + i) for i, reg in enumerate(regs)]),
223                'Expected consecutive staging registers, got {op}')
224
225        if sr.count == 0:
226            modifier_map["staging_register_count"] = sr_count
227        else:
228            die_if(sr_count != sr.count, f"Expected 4 staging registers, got {sr_count}")
229
230        encoded |= ((sr.encoded_flags | base) << sr.start)
231    operands = operands[len(ins.staging):]
232
233    for op, dest in zip(operands, ins.dests):
234        encoded |= encode_dest(op) << 40
235    operands = operands[len(ins.dests):]
236
237    if len(ins.dests) == 0 and len(ins.staging) == 0:
238        # Set a placeholder writemask to prevent encoding faults
239        encoded |= (0xC0 << 40)
240
241    fau = FAUState(immediate_mode)
242
243    for i, (op, src) in enumerate(zip(operands, ins.srcs)):
244        parts = op.split('.')
245        encoded |= encode_source(parts[0], fau) << (i * 8)
246
247        # Has a swizzle been applied yet?
248        swizzled = False
249
250        for mod in parts[1:]:
251            # Encode the modifier
252            if mod in src.offset and src.bits[mod] == 1:
253                encoded |= (1 << src.offset[mod])
254            elif mod in enums[f'swizzles_{src.size}_bit'].bare_values and (src.widen or src.lanes):
255                die_if(swizzled, "Multiple swizzles specified")
256                swizzled = True
257                val = enums[f'swizzles_{src.size}_bit'].bare_values.index(mod)
258                encoded |= (val << src.offset['widen'])
259            elif src.lane and mod in enums[f'lane_{src.size}_bit'].bare_values:
260                die_if(swizzled, "Multiple swizzles specified")
261                swizzled = True
262                val = enums[f'lane_{src.size}_bit'].bare_values.index(mod)
263                encoded |= (val << src.offset['lane'])
264            elif src.size == 32 and mod in enums['widen'].bare_values:
265                die_if(not src.swizzle, "Instruction doesn't take widens")
266                die_if(swizzled, "Multiple swizzles specified")
267                swizzled = True
268                val = enums['widen'].bare_values.index(mod)
269                encoded |= (val << src.offset['swizzle'])
270            elif src.size == 16 and mod in enums['swizzles_16_bit'].bare_values:
271                die_if(not src.swizzle, "Instruction doesn't take swizzles")
272                die_if(swizzled, "Multiple swizzles specified")
273                swizzled = True
274                val = enums['swizzles_16_bit'].bare_values.index(mod)
275                encoded |= (val << src.offset['swizzle'])
276            elif mod in enums['lane_8_bit'].bare_values:
277                die_if(not src.lane, "Instruction doesn't take a lane")
278                die_if(swizzled, "Multiple swizzles specified")
279                swizzled = True
280                val = enums['lane_8_bit'].bare_values.index(mod)
281                encoded |= (val << src.lane)
282            else:
283                die(f"Unknown modifier {mod}")
284
285        # Encode the identity if a swizzle is required but not specified
286        if src.swizzle and not swizzled and src.size == 16:
287            mod = enums['swizzles_16_bit'].default
288            val = enums['swizzles_16_bit'].bare_values.index(mod)
289            encoded |= (val << src.offset['swizzle'])
290        elif src.widen and not swizzled and src.size == 16:
291            die_if(swizzled, "Multiple swizzles specified")
292            mod = enums['swizzles_16_bit'].default
293            val = enums['swizzles_16_bit'].bare_values.index(mod)
294            encoded |= (val << src.offset['widen'])
295
296    operands = operands[len(ins.srcs):]
297
298    for i, (op, imm) in enumerate(zip(operands, ins.immediates)):
299        if op[0] == '#':
300            die_if(imm.name != 'constant', "Wrong syntax for immediate")
301            parts = [imm.name, op[1:]]
302        else:
303            parts = op.split(':')
304            die_if(len(parts) != 2, f"Wrong syntax for immediate, wrong number of colons in {op}")
305            die_if(parts[0] != imm.name, f"Wrong immediate, expected {imm.name}, got {parts[0]}")
306
307        if imm.signed:
308            minimum = -(1 << (imm.size - 1))
309            maximum = +(1 << (imm.size - 1)) - 1
310        else:
311            minimum = 0
312            maximum = (1 << imm.size) - 1
313
314        val = parse_int(parts[1], minimum, maximum)
315
316        if val < 0:
317            # Sign extends
318            val = (1 << imm.size) + val
319
320        encoded |= (val << imm.start)
321
322    operands = operands[len(ins.immediates):]
323
324    # Encode the operation itself
325    encoded |= (ins.opcode << 48)
326    encoded |= (ins.opcode2 << ins.secondary_shift)
327
328    # Encode modifiers
329    has_action = False
330    for mod in mods:
331        if len(mod) == 0:
332            continue
333
334        if mod in enums['action'].bare_values:
335            die_if(has_action, "Multiple actions specified")
336            has_action = True
337            encoded |= (enums['action'].bare_values.index(mod) << 59)
338            encoded |= (1 << 62) # Action, not wait
339        elif mod.startswith('wait'):
340            die_if(has_action, "Multiple actions specified")
341            has_action = True
342
343            slots = mod[len('wait'):]
344            try:
345                slots = set([int(x) for x in slots])
346            except ValueError:
347                die(f"Expected slots in {mod}")
348
349            known_slots = set([0, 1, 2])
350            die_if(not slots.issubset(known_slots), f"Unknown slots in {mod}")
351
352            if 0 in slots:
353                encoded |= (1 << 59)
354            if 1 in slots:
355                encoded |= (1 << 60)
356            if 2 in slots:
357                encoded |= (1 << 61)
358        elif mod in enums['immediate_mode'].bare_values:
359            pass # handled specially
360        else:
361            candidates = [c for c in ins.modifiers if mod in c.bare_values]
362
363            die_if(len(candidates) == 0, f"Invalid modifier {mod} used")
364            assert(len(candidates) == 1) # No ambiguous modifiers
365            opts = candidates[0]
366
367            value = opts.bare_values.index(mod)
368            assert(value is not None)
369
370            die_if(opts.name in modifier_map, f"{opts.name} specified twice")
371            modifier_map[opts.name] = value
372
373    for mod in ins.modifiers:
374        value = modifier_map.get(mod.name, mod.default)
375        die_if(value is None, f"Missing required modifier {mod.name}")
376
377        assert(value < (1 << mod.size))
378        encoded |= (value << mod.start)
379
380    encoded |= (enums['immediate_mode'].bare_values.index(immediate_mode) << 57)
381    return encoded
382
383if __name__ == "__main__":
384    # Provide commandline interface
385    parser = argparse.ArgumentParser(description='Assemble Valhall shaders')
386    parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
387                        default=sys.stdin)
388    parser.add_argument('outfile', type=argparse.FileType('wb'))
389    args = parser.parse_args()
390
391    lines = args.infile.read().strip().split('\n')
392    lines = [l for l in lines if len(l) > 0 and l[0] != '#']
393
394    packed = b''.join([struct.pack('<Q', parse_asm(ln)) for ln in lines])
395    args.outfile.write(packed)
396