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 27# Consider pseudo instructions when getting the modifier list 28instructions_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 33ir_instructions_with_pseudo = partition_mnemonics(instructions_with_pseudo) 34modifier_lists = order_modifiers(ir_instructions_with_pseudo) 35 36# ...but strip for packing 37instructions = parse_instructions(sys.argv[2]) # skip the pseudo instructions in sys.argv[1] 38ir_instructions = partition_mnemonics(instructions) 39 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']), 101 set(['h01', 'none', 'b0123', 'w0']), # Identity 102 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) 119 elif raw == "lane_dest": 120 lists = [pick_from_bucket(opts, bucket) for bucket in SWIZZLE_BUCKETS] 121 ir_value = "dest->swizzle" 122 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)) 287 common_body.append('assert({} < {});'.format(name, hex(1 << width))) 288 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) 322 return bi_pack_${opname_to_c(unit + 'NOP')}(I, src0, src1, src2, src3); 323 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)) 347