Alyssa Rosenzweig | 82328a5 | 2020-12-04 14:54:00 -0500 | [diff] [blame] | 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 | |
| 23 | import sys |
| 24 | from bifrost_isa import * |
| 25 | from mako.template import Template |
| 26 | |
Alyssa Rosenzweig | 3485b8d | 2022-03-23 18:12:31 -0400 | [diff] [blame] | 27 | # Consider pseudo instructions when getting the modifier list |
Eric R. Smith | 1ae8ac3 | 2024-07-04 21:50:33 +0000 | [diff] [blame] | 28 | instructions_with_pseudo = {} |
| 29 | for arg in sys.argv[1:]: |
| 30 | new_instructions = parse_instructions(arg, include_pseudo = True) |
| 31 | instructions_with_pseudo.update(new_instructions) |
| 32 | |
Alyssa Rosenzweig | 3485b8d | 2022-03-23 18:12:31 -0400 | [diff] [blame] | 33 | ir_instructions_with_pseudo = partition_mnemonics(instructions_with_pseudo) |
| 34 | modifier_lists = order_modifiers(ir_instructions_with_pseudo) |
| 35 | |
| 36 | # ...but strip for packing |
Eric R. Smith | 1ae8ac3 | 2024-07-04 21:50:33 +0000 | [diff] [blame] | 37 | instructions = parse_instructions(sys.argv[2]) # skip the pseudo instructions in sys.argv[1] |
Alyssa Rosenzweig | 82328a5 | 2020-12-04 14:54:00 -0500 | [diff] [blame] | 38 | ir_instructions = partition_mnemonics(instructions) |
Alyssa Rosenzweig | 82328a5 | 2020-12-04 14:54:00 -0500 | [diff] [blame] | 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) |
| 43 | def 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` |
| 59 | def 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 | |
| 84 | def 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 | |
| 89 | def 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']), |
Icecream95 | 2e372d0 | 2020-12-29 01:09:04 +1300 | [diff] [blame] | 101 | set(['h01', 'none', 'b0123', 'w0']), # Identity |
Alyssa Rosenzweig | 82328a5 | 2020-12-04 14:54:00 -0500 | [diff] [blame] | 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) |
Icecream95 | b361a80 | 2021-01-03 21:53:05 +1300 | [diff] [blame] | 119 | elif raw == "lane_dest": |
| 120 | lists = [pick_from_bucket(opts, bucket) for bucket in SWIZZLE_BUCKETS] |
| 121 | ir_value = "dest->swizzle" |
Alyssa Rosenzweig | 82328a5 | 2020-12-04 14:54:00 -0500 | [diff] [blame] | 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 | |
| 139 | SEXPR_BINARY = { |
| 140 | "and": "&&", |
| 141 | "or": "||", |
| 142 | "eq": "==", |
| 143 | "neq": "!=" |
| 144 | } |
| 145 | |
| 146 | def 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 | |
| 170 | def 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 | |
| 196 | variant_template = Template("""static inline unsigned |
| 197 | bi_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 | |
| 216 | def 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 Rosenzweig | 455cde2 | 2021-01-11 13:50:43 -0500 | [diff] [blame] | 287 | common_body.append('assert({} < {});'.format(name, hex(1 << width))) |
Alyssa Rosenzweig | 82328a5 | 2020-12-04 14:54:00 -0500 | [diff] [blame] | 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 | |
| 308 | print(COPYRIGHT + '#include "compiler.h"') |
| 309 | |
| 310 | packs = [pack_variant(e, instructions[e]) for e in instructions] |
| 311 | for p in packs: |
| 312 | print(p) |
| 313 | |
| 314 | top_pack = Template("""unsigned |
| 315 | bi_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 Rosenzweig | f9616b7 | 2021-07-23 12:41:52 -0400 | [diff] [blame] | 322 | return bi_pack_${opname_to_c(unit + 'NOP')}(I, src0, src1, src2, src3); |
Alyssa Rosenzweig | 82328a5 | 2020-12-04 14:54:00 -0500 | [diff] [blame] | 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 | |
| 345 | for unit in ['*', '+']: |
| 346 | print(top_pack.render(ops = ir_instructions, instructions = instructions, opname_to_c = opname_to_c, unit = unit)) |