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