blob: c50606398099c500faea1dc1ecd224ca4d7b00ad [file] [log] [blame]
Alyssa Rosenzweig82328a52020-12-04 14:54:00 -05001#
2# Copyright (C) 2020 Collabora, Ltd.
3#
4# Permission is hereby granted, free of charge, to any person obtaining a
5# copy of this software and associated documentation files (the "Software"),
6# to deal in the Software without restriction, including without limitation
7# the rights to use, copy, modify, merge, publish, distribute, sublicense,
8# and/or sell copies of the Software, and to permit persons to whom the
9# Software is furnished to do so, subject to the following conditions:
10#
11# The above copyright notice and this permission notice (including the next
12# paragraph) shall be included in all copies or substantial portions of the
13# Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21# IN THE SOFTWARE.
22
23import sys
24from bifrost_isa import *
25from mako.template import Template
26
Alyssa Rosenzweig3485b8d2022-03-23 18:12:31 -040027# Consider pseudo instructions when getting the modifier list
Eric R. Smith1ae8ac32024-07-04 21:50:33 +000028instructions_with_pseudo = {}
29for arg in sys.argv[1:]:
30 new_instructions = parse_instructions(arg, include_pseudo = True)
31 instructions_with_pseudo.update(new_instructions)
32
Alyssa Rosenzweig3485b8d2022-03-23 18:12:31 -040033ir_instructions_with_pseudo = partition_mnemonics(instructions_with_pseudo)
34modifier_lists = order_modifiers(ir_instructions_with_pseudo)
35
36# ...but strip for packing
Eric R. Smith1ae8ac32024-07-04 21:50:33 +000037instructions = parse_instructions(sys.argv[2]) # skip the pseudo instructions in sys.argv[1]
Alyssa Rosenzweig82328a52020-12-04 14:54:00 -050038ir_instructions = partition_mnemonics(instructions)
Alyssa Rosenzweig82328a52020-12-04 14:54:00 -050039
40# Packs sources into an argument. Offset argument to work around a quirk of our
41# compiler IR when dealing with staging registers (TODO: reorder in the IR to
42# fix this)
43def pack_sources(sources, body, pack_exprs, offset, is_fma):
44 for i, src in enumerate(sources):
45 # FMA first two args are restricted, but that's checked once for all
46 # FMA so the compiler has less work to do
47 expected = 0xFB if (is_fma and i < 2) else 0xFF
48
49 # Validate the source
50 if src[1] != expected:
51 assert((src[1] & expected) == src[1])
52 body.append('assert((1 << src{}) & {});'.format(i, hex(src[1])))
53
54 # Sources are state-invariant
55 for state in pack_exprs:
56 state.append('(src{} << {})'.format(i, src[0]))
57
58# Try to map from a modifier list `domain` to the list `target`
59def map_modifier(body, prefix, mod, domain, target):
60 # We don't want to map reserveds, that's invalid IR anyway
61 def reserved_to_none(arr):
62 return [None if x == 'reserved' else x for x in arr]
63
64 # Trim out reserveds at the end
65 noned_domain = reserved_to_none(domain)
66 noned_target = reserved_to_none(target)
67 none_indices = [i for i, x in enumerate(noned_target) if x != None]
68 trimmed = noned_target[0: none_indices[-1] + 1]
69
70 if trimmed == noned_domain[0:len(trimmed)]:
71 # Identity map, possibly on the left subset
72 return mod
73 else:
74 # Generate a table as a fallback
75 table = ", ".join([str(target.index(x)) if x in target else "~0" for x in domain])
76 body.append("static uint8_t {}_table[] = {{ {} }};".format(prefix, table))
77
78 if len(domain) > 2:
79 # no need to validate bools
80 body.append("assert({} < {});".format(mod, len(domain)))
81
82 return "{}_table[{}]".format(prefix, mod)
83
84def pick_from_bucket(opts, bucket):
85 intersection = set(opts) & bucket
86 assert(len(intersection) <= 1)
87 return intersection.pop() if len(intersection) == 1 else None
88
89def pack_modifier(mod, width, default, opts, body, pack_exprs):
90 # Destructure the modifier name
91 (raw, arg) = (mod[0:-1], mod[-1]) if mod[-1] in "0123" else (mod, 0)
92
93 SWIZZLES = ["lane", "lanes", "replicate", "swz", "widen", "swap"]
94
95 ir_value = "bytes2" if mod == "bytes2" else "{}[{}]".format(raw, arg) if mod[-1] in "0123" else mod
96 lists = modifier_lists[raw]
97
98 # Swizzles need to be packed "specially"
99 SWIZZLE_BUCKETS = [
100 set(['h00', 'h0']),
Icecream952e372d02020-12-29 01:09:04 +1300101 set(['h01', 'none', 'b0123', 'w0']), # Identity
Alyssa Rosenzweig82328a52020-12-04 14:54:00 -0500102 set(['h10']),
103 set(['h11', 'h1']),
104 set(['b0000', 'b00', 'b0']),
105 set(['b1111', 'b11', 'b1']),
106 set(['b2222', 'b22', 'b2']),
107 set(['b3333', 'b33', 'b3']),
108 set(['b0011', 'b01']),
109 set(['b2233', 'b23']),
110 set(['b1032']),
111 set(['b3210']),
112 set(['b0022', 'b02'])
113 ]
114
115 if raw in SWIZZLES:
116 # Construct a list
117 lists = [pick_from_bucket(opts, bucket) for bucket in SWIZZLE_BUCKETS]
118 ir_value = "src[{}].swizzle".format(arg)
Icecream95b361a802021-01-03 21:53:05 +1300119 elif raw == "lane_dest":
120 lists = [pick_from_bucket(opts, bucket) for bucket in SWIZZLE_BUCKETS]
121 ir_value = "dest->swizzle"
Alyssa Rosenzweig82328a52020-12-04 14:54:00 -0500122 elif raw in ["abs", "sign"]:
123 ir_value = "src[{}].abs".format(arg)
124 elif raw in ["neg", "not"]:
125 ir_value = "src[{}].neg".format(arg)
126
127 ir_value = "I->{}".format(ir_value)
128
129 # We need to map from ir_opts to opts
130 mapped = map_modifier(body, mod, ir_value, lists, opts)
131 body.append('unsigned {} = {};'.format(mod, mapped))
132 body.append('assert({} < {});'.format(mod, 1 << width))
133
134# Compiles an S-expression (and/or/eq/neq, modifiers, `ordering`, immediates)
135# into a C boolean expression suitable to stick in an if-statement. Takes an
136# imm_map to map modifiers to immediate values, parametrized by the ctx that
137# we're looking up in (the first, non-immediate argument of the equality)
138
139SEXPR_BINARY = {
140 "and": "&&",
141 "or": "||",
142 "eq": "==",
143 "neq": "!="
144}
145
146def compile_s_expr(expr, imm_map, ctx):
147 if expr[0] == 'alias':
148 return compile_s_expr(expr[1], imm_map, ctx)
149 elif expr == ['eq', 'ordering', '#gt']:
150 return '(src0 > src1)'
151 elif expr == ['neq', 'ordering', '#lt']:
152 return '(src0 >= src1)'
153 elif expr == ['neq', 'ordering', '#gt']:
154 return '(src0 <= src1)'
155 elif expr == ['eq', 'ordering', '#lt']:
156 return '(src0 < src1)'
157 elif expr == ['eq', 'ordering', '#eq']:
158 return '(src0 == src1)'
159 elif isinstance(expr, list):
160 sep = " {} ".format(SEXPR_BINARY[expr[0]])
161 return "(" + sep.join([compile_s_expr(s, imm_map, expr[1]) for s in expr[1:]]) + ")"
162 elif expr[0] == '#':
163 return str(imm_map[ctx][expr[1:]])
164 else:
165 return expr
166
167# Packs a derived value. We just iterate through the possible choices and test
168# whether the encoding matches, and if so we use it.
169
170def pack_derived(pos, exprs, imm_map, body, pack_exprs):
171 body.append('unsigned derived_{} = 0;'.format(pos))
172
173 first = True
174 for i, expr in enumerate(exprs):
175 if expr is not None:
176 cond = compile_s_expr(expr, imm_map, None)
177 body.append('{}if {} derived_{} = {};'.format('' if first else 'else ', cond, pos, i))
178 first = False
179
180 assert (not first)
181 body.append('else unreachable("No pattern match at pos {}");'.format(pos))
182 body.append('')
183
184 assert(pos is not None)
185 pack_exprs.append('(derived_{} << {})'.format(pos, pos))
186
187# Generates a routine to pack a single variant of a single- instruction.
188# Template applies the needed formatting and combine to OR together all the
189# pack_exprs to avoid bit fields.
190#
191# Argument swapping is sensitive to the order of operations. Dependencies:
192# sources (RW), modifiers (RW), derived values (W). Hence we emit sources and
193# modifiers first, then perform a swap if necessary overwriting
194# sources/modifiers, and last calculate derived values and pack.
195
196variant_template = Template("""static inline unsigned
197bi_pack_${name}(${", ".join(["bi_instr *I"] + ["enum bifrost_packed_src src{}".format(i) for i in range(srcs)])})
198{
199${"\\n".join([(" " + x) for x in common_body])}
200% if single_state:
201% for (pack_exprs, s_body, _) in states:
202${"\\n".join([" " + x for x in s_body + ["return {};".format( " | ".join(pack_exprs))]])}
203% endfor
204% else:
205% for i, (pack_exprs, s_body, cond) in enumerate(states):
206 ${'} else ' if i > 0 else ''}if ${cond} {
207${"\\n".join([" " + x for x in s_body + ["return {};".format(" | ".join(pack_exprs))]])}
208% endfor
209 } else {
210 unreachable("No matching state found in ${name}");
211 }
212% endif
213}
214""")
215
216def pack_variant(opname, states):
217 # Expressions to be ORed together for the final pack, an array per state
218 pack_exprs = [[hex(state[1]["exact"][1])] for state in states]
219
220 # Computations which need to be done to encode first, across states
221 common_body = []
222
223 # Map from modifier names to a map from modifier values to encoded values
224 # String -> { String -> Uint }. This can be shared across states since
225 # modifiers are (except the pos values) constant across state.
226 imm_map = {}
227
228 # Pack sources. Offset over to deal with staging/immediate weirdness in our
229 # IR (TODO: reorder sources upstream so this goes away). Note sources are
230 # constant across states.
231 staging = states[0][1].get("staging", "")
232 offset = 0
233 if staging in ["r", "rw"]:
234 offset += 1
235
236 pack_sources(states[0][1].get("srcs", []), common_body, pack_exprs, offset, opname[0] == '*')
237
238 modifiers_handled = []
239 for st in states:
240 for ((mod, _, width), default, opts) in st[1].get("modifiers", []):
241 if mod in modifiers_handled:
242 continue
243
244 modifiers_handled.append(mod)
245 pack_modifier(mod, width, default, opts, common_body, pack_exprs)
246
247 imm_map[mod] = { x: y for y, x in enumerate(opts) }
248
249 for i, st in enumerate(states):
250 for ((mod, pos, width), default, opts) in st[1].get("modifiers", []):
251 if pos is not None:
252 pack_exprs[i].append('({} << {})'.format(mod, pos))
253
254 for ((src_a, src_b), cond, remap) in st[1].get("swaps", []):
255 # Figure out which vars to swap, in order to swap the arguments. This
256 # always includes the sources themselves, and may include source
257 # modifiers (with the same source indices). We swap based on which
258 # matches A, this is arbitrary but if we swapped both nothing would end
259 # up swapping at all since it would swap back.
260
261 vars_to_swap = ['src']
262 for ((mod, _, width), default, opts) in st[1].get("modifiers", []):
263 if mod[-1] in str(src_a):
264 vars_to_swap.append(mod[0:-1])
265
266 common_body.append('if {}'.format(compile_s_expr(cond, imm_map, None)) + ' {')
267
268 # Emit the swaps. We use a temp, and wrap in a block to avoid naming
269 # collisions with multiple swaps. {{Doubling}} to escape the format.
270
271 for v in vars_to_swap:
272 common_body.append(' {{ unsigned temp = {}{}; {}{} = {}{}; {}{} = temp; }}'.format(v, src_a, v, src_a, v, src_b, v, src_b))
273
274 # Also, remap. Bidrectional swaps are explicit in the XML.
275 for v in remap:
276 maps = remap[v]
277 imm = imm_map[v]
278
279 for i, l in enumerate(maps):
280 common_body.append(' {}if ({} == {}) {} = {};'.format('' if i == 0 else 'else ', v, imm[l], v, imm[maps[l]]))
281
282 common_body.append('}')
283 common_body.append('')
284
285 for (name, pos, width) in st[1].get("immediates", []):
286 common_body.append('unsigned {} = I->{};'.format(name, name))
Alyssa Rosenzweig455cde22021-01-11 13:50:43 -0500287 common_body.append('assert({} < {});'.format(name, hex(1 << width)))
Alyssa Rosenzweig82328a52020-12-04 14:54:00 -0500288
289 for st in pack_exprs:
290 st.append('({} << {})'.format(name, pos))
291
292 # After this, we have to branch off, since deriveds *do* vary based on state.
293 state_body = [[] for s in states]
294
295 for i, (_, st) in enumerate(states):
296 for ((pos, width), exprs) in st.get("derived", []):
297 pack_derived(pos, exprs, imm_map, state_body[i], pack_exprs[i])
298
299 # How do we pick a state? Accumulate the conditions
300 state_conds = [compile_s_expr(st[0], imm_map, None) for st in states] if len(states) > 1 else [None]
301
302 if state_conds == None:
303 assert (states[0][0] == None)
304
305 # Finally, we'll collect everything together
306 return variant_template.render(name = opname_to_c(opname), states = zip(pack_exprs, state_body, state_conds), common_body = common_body, single_state = (len(states) == 1), srcs = 4)
307
308print(COPYRIGHT + '#include "compiler.h"')
309
310packs = [pack_variant(e, instructions[e]) for e in instructions]
311for p in packs:
312 print(p)
313
314top_pack = Template("""unsigned
315bi_pack_${'fma' if unit == '*' else 'add'}(bi_instr *I,
316 enum bifrost_packed_src src0,
317 enum bifrost_packed_src src1,
318 enum bifrost_packed_src src2,
319 enum bifrost_packed_src src3)
320{
321 if (!I)
Alyssa Rosenzweigf9616b72021-07-23 12:41:52 -0400322 return bi_pack_${opname_to_c(unit + 'NOP')}(I, src0, src1, src2, src3);
Alyssa Rosenzweig82328a52020-12-04 14:54:00 -0500323
324% if unit == '*':
325 assert((1 << src0) & 0xfb);
326 assert((1 << src1) & 0xfb);
327
328% endif
329 switch (I->op) {
330% for opcode in ops:
331% if unit + opcode in instructions:
332 case BI_OPCODE_${opcode.replace('.', '_').upper()}:
333 return bi_pack_${opname_to_c(unit + opcode)}(I, src0, src1, src2, src3);
334% endif
335% endfor
336 default:
337#ifndef NDEBUG
338 bi_print_instr(I, stderr);
339#endif
340 unreachable("Cannot pack instruction as ${unit}");
341 }
342}
343""")
344
345for unit in ['*', '+']:
346 print(top_pack.render(ops = ir_instructions, instructions = instructions, opname_to_c = opname_to_c, unit = unit))