1# 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 27instructions = parse_instructions(sys.argv[1]) 28ir_instructions = partition_mnemonics(instructions) 29modifier_lists = order_modifiers(ir_instructions) 30 31# Packs sources into an argument. Offset argument to work around a quirk of our 32# compiler IR when dealing with staging registers (TODO: reorder in the IR to 33# fix this) 34def pack_sources(sources, body, pack_exprs, offset, is_fma): 35 for i, src in enumerate(sources): 36 # FMA first two args are restricted, but that's checked once for all 37 # FMA so the compiler has less work to do 38 expected = 0xFB if (is_fma and i < 2) else 0xFF 39 40 # Validate the source 41 if src[1] != expected: 42 assert((src[1] & expected) == src[1]) 43 body.append('assert((1 << src{}) & {});'.format(i, hex(src[1]))) 44 45 # Sources are state-invariant 46 for state in pack_exprs: 47 state.append('(src{} << {})'.format(i, src[0])) 48 49# Try to map from a modifier list `domain` to the list `target` 50def map_modifier(body, prefix, mod, domain, target): 51 # We don't want to map reserveds, that's invalid IR anyway 52 def reserved_to_none(arr): 53 return [None if x == 'reserved' else x for x in arr] 54 55 # Trim out reserveds at the end 56 noned_domain = reserved_to_none(domain) 57 noned_target = reserved_to_none(target) 58 none_indices = [i for i, x in enumerate(noned_target) if x != None] 59 trimmed = noned_target[0: none_indices[-1] + 1] 60 61 if trimmed == noned_domain[0:len(trimmed)]: 62 # Identity map, possibly on the left subset 63 return mod 64 else: 65 # Generate a table as a fallback 66 table = ", ".join([str(target.index(x)) if x in target else "~0" for x in domain]) 67 body.append("static uint8_t {}_table[] = {{ {} }};".format(prefix, table)) 68 69 if len(domain) > 2: 70 # no need to validate bools 71 body.append("assert({} < {});".format(mod, len(domain))) 72 73 return "{}_table[{}]".format(prefix, mod) 74 75def pick_from_bucket(opts, bucket): 76 intersection = set(opts) & bucket 77 assert(len(intersection) <= 1) 78 return intersection.pop() if len(intersection) == 1 else None 79 80def pack_modifier(mod, width, default, opts, body, pack_exprs): 81 # Destructure the modifier name 82 (raw, arg) = (mod[0:-1], mod[-1]) if mod[-1] in "0123" else (mod, 0) 83 84 SWIZZLES = ["lane", "lanes", "replicate", "swz", "widen", "swap"] 85 86 ir_value = "bytes2" if mod == "bytes2" else "{}[{}]".format(raw, arg) if mod[-1] in "0123" else mod 87 lists = modifier_lists[raw] 88 89 # Swizzles need to be packed "specially" 90 SWIZZLE_BUCKETS = [ 91 set(['h00', 'h0']), 92 set(['h01', 'none', 'b0123', 'w0']), # Identity 93 set(['h10']), 94 set(['h11', 'h1']), 95 set(['b0000', 'b00', 'b0']), 96 set(['b1111', 'b11', 'b1']), 97 set(['b2222', 'b22', 'b2']), 98 set(['b3333', 'b33', 'b3']), 99 set(['b0011', 'b01']), 100 set(['b2233', 'b23']), 101 set(['b1032']), 102 set(['b3210']), 103 set(['b0022', 'b02']) 104 ] 105 106 if raw in SWIZZLES: 107 # Construct a list 108 lists = [pick_from_bucket(opts, bucket) for bucket in SWIZZLE_BUCKETS] 109 ir_value = "src[{}].swizzle".format(arg) 110 elif raw == "lane_dest": 111 lists = [pick_from_bucket(opts, bucket) for bucket in SWIZZLE_BUCKETS] 112 ir_value = "dest->swizzle" 113 elif raw in ["abs", "sign"]: 114 ir_value = "src[{}].abs".format(arg) 115 elif raw in ["neg", "not"]: 116 ir_value = "src[{}].neg".format(arg) 117 118 ir_value = "I->{}".format(ir_value) 119 120 # We need to map from ir_opts to opts 121 mapped = map_modifier(body, mod, ir_value, lists, opts) 122 body.append('unsigned {} = {};'.format(mod, mapped)) 123 body.append('assert({} < {});'.format(mod, 1 << width)) 124 125# Compiles an S-expression (and/or/eq/neq, modifiers, `ordering`, immediates) 126# into a C boolean expression suitable to stick in an if-statement. Takes an 127# imm_map to map modifiers to immediate values, parametrized by the ctx that 128# we're looking up in (the first, non-immediate argument of the equality) 129 130SEXPR_BINARY = { 131 "and": "&&", 132 "or": "||", 133 "eq": "==", 134 "neq": "!=" 135} 136 137def compile_s_expr(expr, imm_map, ctx): 138 if expr[0] == 'alias': 139 return compile_s_expr(expr[1], imm_map, ctx) 140 elif expr == ['eq', 'ordering', '#gt']: 141 return '(src0 > src1)' 142 elif expr == ['neq', 'ordering', '#lt']: 143 return '(src0 >= src1)' 144 elif expr == ['neq', 'ordering', '#gt']: 145 return '(src0 <= src1)' 146 elif expr == ['eq', 'ordering', '#lt']: 147 return '(src0 < src1)' 148 elif expr == ['eq', 'ordering', '#eq']: 149 return '(src0 == src1)' 150 elif isinstance(expr, list): 151 sep = " {} ".format(SEXPR_BINARY[expr[0]]) 152 return "(" + sep.join([compile_s_expr(s, imm_map, expr[1]) for s in expr[1:]]) + ")" 153 elif expr[0] == '#': 154 return str(imm_map[ctx][expr[1:]]) 155 else: 156 return expr 157 158# Packs a derived value. We just iterate through the possible choices and test 159# whether the encoding matches, and if so we use it. 160 161def pack_derived(pos, exprs, imm_map, body, pack_exprs): 162 body.append('unsigned derived_{} = 0;'.format(pos)) 163 164 first = True 165 for i, expr in enumerate(exprs): 166 if expr is not None: 167 cond = compile_s_expr(expr, imm_map, None) 168 body.append('{}if {} derived_{} = {};'.format('' if first else 'else ', cond, pos, i)) 169 first = False 170 171 assert (not first) 172 body.append('else unreachable("No pattern match at pos {}");'.format(pos)) 173 body.append('') 174 175 assert(pos is not None) 176 pack_exprs.append('(derived_{} << {})'.format(pos, pos)) 177 178# Generates a routine to pack a single variant of a single- instruction. 179# Template applies the needed formatting and combine to OR together all the 180# pack_exprs to avoid bit fields. 181# 182# Argument swapping is sensitive to the order of operations. Dependencies: 183# sources (RW), modifiers (RW), derived values (W). Hence we emit sources and 184# modifiers first, then perform a swap if necessary overwriting 185# sources/modifiers, and last calculate derived values and pack. 186 187variant_template = Template("""static inline unsigned 188bi_pack_${name}(${", ".join(["bi_instr *I"] + ["enum bifrost_packed_src src{}".format(i) for i in range(srcs)])}) 189{ 190${"\\n".join([(" " + x) for x in common_body])} 191% if single_state: 192% for (pack_exprs, s_body, _) in states: 193${"\\n".join([" " + x for x in s_body + ["return {};".format( " | ".join(pack_exprs))]])} 194% endfor 195% else: 196% for i, (pack_exprs, s_body, cond) in enumerate(states): 197 ${'} else ' if i > 0 else ''}if ${cond} { 198${"\\n".join([" " + x for x in s_body + ["return {};".format(" | ".join(pack_exprs))]])} 199% endfor 200 } else { 201 unreachable("No matching state found in ${name}"); 202 } 203% endif 204} 205""") 206 207def pack_variant(opname, states): 208 # Expressions to be ORed together for the final pack, an array per state 209 pack_exprs = [[hex(state[1]["exact"][1])] for state in states] 210 211 # Computations which need to be done to encode first, across states 212 common_body = [] 213 214 # Map from modifier names to a map from modifier values to encoded values 215 # String -> { String -> Uint }. This can be shared across states since 216 # modifiers are (except the pos values) constant across state. 217 imm_map = {} 218 219 # Pack sources. Offset over to deal with staging/immediate weirdness in our 220 # IR (TODO: reorder sources upstream so this goes away). Note sources are 221 # constant across states. 222 staging = states[0][1].get("staging", "") 223 offset = 0 224 if staging in ["r", "rw"]: 225 offset += 1 226 227 pack_sources(states[0][1].get("srcs", []), common_body, pack_exprs, offset, opname[0] == '*') 228 229 modifiers_handled = [] 230 for st in states: 231 for ((mod, _, width), default, opts) in st[1].get("modifiers", []): 232 if mod in modifiers_handled: 233 continue 234 235 modifiers_handled.append(mod) 236 pack_modifier(mod, width, default, opts, common_body, pack_exprs) 237 238 imm_map[mod] = { x: y for y, x in enumerate(opts) } 239 240 for i, st in enumerate(states): 241 for ((mod, pos, width), default, opts) in st[1].get("modifiers", []): 242 if pos is not None: 243 pack_exprs[i].append('({} << {})'.format(mod, pos)) 244 245 for ((src_a, src_b), cond, remap) in st[1].get("swaps", []): 246 # Figure out which vars to swap, in order to swap the arguments. This 247 # always includes the sources themselves, and may include source 248 # modifiers (with the same source indices). We swap based on which 249 # matches A, this is arbitrary but if we swapped both nothing would end 250 # up swapping at all since it would swap back. 251 252 vars_to_swap = ['src'] 253 for ((mod, _, width), default, opts) in st[1].get("modifiers", []): 254 if mod[-1] in str(src_a): 255 vars_to_swap.append(mod[0:-1]) 256 257 common_body.append('if {}'.format(compile_s_expr(cond, imm_map, None)) + ' {') 258 259 # Emit the swaps. We use a temp, and wrap in a block to avoid naming 260 # collisions with multiple swaps. {{Doubling}} to escape the format. 261 262 for v in vars_to_swap: 263 common_body.append(' {{ unsigned temp = {}{}; {}{} = {}{}; {}{} = temp; }}'.format(v, src_a, v, src_a, v, src_b, v, src_b)) 264 265 # Also, remap. Bidrectional swaps are explicit in the XML. 266 for v in remap: 267 maps = remap[v] 268 imm = imm_map[v] 269 270 for i, l in enumerate(maps): 271 common_body.append(' {}if ({} == {}) {} = {};'.format('' if i == 0 else 'else ', v, imm[l], v, imm[maps[l]])) 272 273 common_body.append('}') 274 common_body.append('') 275 276 for (name, pos, width) in st[1].get("immediates", []): 277 common_body.append('unsigned {} = I->{};'.format(name, name)) 278 common_body.append('assert({} < {});'.format(name, hex(1 << width))) 279 280 for st in pack_exprs: 281 st.append('({} << {})'.format(name, pos)) 282 283 # After this, we have to branch off, since deriveds *do* vary based on state. 284 state_body = [[] for s in states] 285 286 for i, (_, st) in enumerate(states): 287 for ((pos, width), exprs) in st.get("derived", []): 288 pack_derived(pos, exprs, imm_map, state_body[i], pack_exprs[i]) 289 290 # How do we pick a state? Accumulate the conditions 291 state_conds = [compile_s_expr(st[0], imm_map, None) for st in states] if len(states) > 1 else [None] 292 293 if state_conds == None: 294 assert (states[0][0] == None) 295 296 # Finally, we'll collect everything together 297 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) 298 299print(COPYRIGHT + '#include "compiler.h"') 300 301packs = [pack_variant(e, instructions[e]) for e in instructions] 302for p in packs: 303 print(p) 304 305top_pack = Template("""unsigned 306bi_pack_${'fma' if unit == '*' else 'add'}(bi_instr *I, 307 enum bifrost_packed_src src0, 308 enum bifrost_packed_src src1, 309 enum bifrost_packed_src src2, 310 enum bifrost_packed_src src3) 311{ 312 if (!I) 313 return bi_pack_${opname_to_c(unit + 'NOP')}(I, src0, src1, src2, src3); 314 315% if unit == '*': 316 assert((1 << src0) & 0xfb); 317 assert((1 << src1) & 0xfb); 318 319% endif 320 switch (I->op) { 321% for opcode in ops: 322% if unit + opcode in instructions: 323 case BI_OPCODE_${opcode.replace('.', '_').upper()}: 324 return bi_pack_${opname_to_c(unit + opcode)}(I, src0, src1, src2, src3); 325% endif 326% endfor 327 default: 328#ifndef NDEBUG 329 bi_print_instr(I, stderr); 330#endif 331 unreachable("Cannot pack instruction as ${unit}"); 332 } 333} 334""") 335 336for unit in ['*', '+']: 337 print(top_pack.render(ops = ir_instructions, instructions = instructions, opname_to_c = opname_to_c, unit = unit)) 338