|
| 1 | +//go:build ignore |
| 2 | + |
| 3 | +package main |
| 4 | + |
| 5 | +import ( |
| 6 | + "bytes" |
| 7 | + "fmt" |
| 8 | + "io" |
| 9 | + "os" |
| 10 | +) |
| 11 | + |
| 12 | +var widths = []int{ |
| 13 | + 2, 3, 4, 5, 6, 7, |
| 14 | + 9, 10, 11, 12, 13, 14, 15, |
| 15 | + 17, 18, 19, 20, 21, 22, 23, |
| 16 | + 25, 26, 27, 28, 29, 30, 31, |
| 17 | +} |
| 18 | + |
| 19 | +type integerType struct { |
| 20 | + name string |
| 21 | + fileName string |
| 22 | + goType string |
| 23 | + load string |
| 24 | + bitwiseAnd string |
| 25 | + valueStride int |
| 26 | + valueShift int |
| 27 | +} |
| 28 | + |
| 29 | +var integerTypes = []integerType{ |
| 30 | + {name: "Int32", fileName: "int32", goType: "int32", load: "MOVL", bitwiseAnd: "ANDL", valueStride: 4, valueShift: 2}, |
| 31 | + {name: "Int64", fileName: "int64", goType: "int64", load: "MOVQ", bitwiseAnd: "ANDQ", valueStride: 8, valueShift: 3}, |
| 32 | +} |
| 33 | + |
| 34 | +func main() { |
| 35 | + for _, integerType := range integerTypes { |
| 36 | + generateGo(integerType) |
| 37 | + generateAssembly(integerType) |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +func generatedWidths(integerType integerType) []int { |
| 42 | + result := append([]int(nil), widths...) |
| 43 | + if integerType.name == "Int32" { |
| 44 | + return append(result, 32) |
| 45 | + } |
| 46 | + for width := 33; width <= 64; width++ { |
| 47 | + result = append(result, width) |
| 48 | + } |
| 49 | + return result |
| 50 | +} |
| 51 | + |
| 52 | +func generateGo(integerType integerType) { |
| 53 | + var out bytes.Buffer |
| 54 | + f := &out |
| 55 | + |
| 56 | + fmt.Fprintln(f, "//go:build !purego") |
| 57 | + fmt.Fprintln(f) |
| 58 | + fmt.Fprintln(f, "// Code generated by gen_pack_amd64.go; DO NOT EDIT.") |
| 59 | + fmt.Fprintln(f) |
| 60 | + fmt.Fprintln(f, "package bitpack") |
| 61 | + fmt.Fprintln(f) |
| 62 | + for _, width := range generatedWidths(integerType) { |
| 63 | + fmt.Fprintln(f, "//go:noescape") |
| 64 | + fmt.Fprintf(f, "func pack%sx%dbitsAMD64(dst []byte, src []%s)\n\n", integerType.name, width, integerType.goType) |
| 65 | + } |
| 66 | + fmt.Fprintf(f, "func pack%sGeneratedAMD64(dst []byte, src []%s, bitWidth uint) {\n", integerType.name, integerType.goType) |
| 67 | + fmt.Fprintln(f, "\tswitch bitWidth {") |
| 68 | + for _, width := range generatedWidths(integerType) { |
| 69 | + fmt.Fprintf(f, "\tcase %d:\n\t\tpack%sx%dbitsAMD64(dst, src)\n", width, integerType.name, width) |
| 70 | + } |
| 71 | + fmt.Fprintln(f, "\t}") |
| 72 | + fmt.Fprintln(f, "}") |
| 73 | + |
| 74 | + fileName := fmt.Sprintf("pack_%s_generated_amd64.go", integerType.fileName) |
| 75 | + if err := os.WriteFile(fileName, out.Bytes(), 0o644); err != nil { |
| 76 | + panic(err) |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +func generateAssembly(integerType integerType) { |
| 81 | + var out bytes.Buffer |
| 82 | + f := &out |
| 83 | + |
| 84 | + fmt.Fprintln(f, "//go:build !purego") |
| 85 | + fmt.Fprintln(f) |
| 86 | + fmt.Fprintln(f, "// Code generated by gen_pack_amd64.go; DO NOT EDIT.") |
| 87 | + fmt.Fprintln(f) |
| 88 | + fmt.Fprintln(f, "#include \"textflag.h\"") |
| 89 | + for _, width := range generatedWidths(integerType) { |
| 90 | + fmt.Fprintln(f) |
| 91 | + emitKernel(f, integerType, width) |
| 92 | + } |
| 93 | + |
| 94 | + fileName := fmt.Sprintf("pack_%s_generated_amd64.s", integerType.fileName) |
| 95 | + if err := os.WriteFile(fileName, out.Bytes(), 0o644); err != nil { |
| 96 | + panic(err) |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +func emitKernel(f io.Writer, integerType integerType, width int) { |
| 101 | + fmt.Fprintf(f, "// func pack%sx%dbitsAMD64(dst []byte, src []%s)\n", integerType.name, width, integerType.goType) |
| 102 | + fmt.Fprintf(f, "TEXT ·pack%sx%dbitsAMD64(SB), NOSPLIT, $0-48\n", integerType.name, width) |
| 103 | + fmt.Fprintln(f, "\tMOVQ dst_base+0(FP), AX") |
| 104 | + fmt.Fprintln(f, "\tMOVQ src_base+24(FP), BX") |
| 105 | + fmt.Fprintln(f, "\tMOVQ src_len+32(FP), DX") |
| 106 | + fmt.Fprintf(f, "\tSHLQ $%d, DX\n", integerType.valueShift) |
| 107 | + fmt.Fprintln(f, "\tADDQ BX, DX") |
| 108 | + if width > 32 && width < 64 { |
| 109 | + fmt.Fprintf(f, "\tMOVQ $0x%x, R10\n", uint64(1)<<width-1) |
| 110 | + } |
| 111 | + fmt.Fprintf(f, "pack_%s_%dbit_amd64_loop:\n", integerType.fileName, width) |
| 112 | + |
| 113 | + switch { |
| 114 | + case width == integerType.valueStride*8: |
| 115 | + emitCopyKernel(f, integerType) |
| 116 | + case width <= 7: |
| 117 | + emitSmallKernel(f, integerType, width) |
| 118 | + case width <= 15: |
| 119 | + emitMediumKernel(f, integerType, width) |
| 120 | + case width <= 21: |
| 121 | + emitLargeKernel(f, integerType, width) |
| 122 | + case width <= 31: |
| 123 | + emitPairKernel(f, integerType, width) |
| 124 | + default: |
| 125 | + emitWideKernel(f, integerType, width) |
| 126 | + } |
| 127 | + |
| 128 | + fmt.Fprintf(f, "\tADDQ $%d, BX\n", 8*integerType.valueStride) |
| 129 | + fmt.Fprintf(f, "\tADDQ $%d, AX\n", width) |
| 130 | + fmt.Fprintln(f, "\tCMPQ BX, DX") |
| 131 | + fmt.Fprintf(f, "\tJB pack_%s_%dbit_amd64_loop\n", integerType.fileName, width) |
| 132 | + fmt.Fprintln(f, "\tRET") |
| 133 | +} |
| 134 | + |
| 135 | +func emitCopyKernel(f io.Writer, integerType integerType) { |
| 136 | + for offset := 0; offset < 8*integerType.valueStride; offset += 16 { |
| 137 | + reg := fmt.Sprintf("X%d", offset/16) |
| 138 | + fmt.Fprintf(f, "\tMOVOU %d(BX), %s\n", offset, reg) |
| 139 | + } |
| 140 | + for offset := 0; offset < 8*integerType.valueStride; offset += 16 { |
| 141 | + reg := fmt.Sprintf("X%d", offset/16) |
| 142 | + fmt.Fprintf(f, "\tMOVOU %s, %d(AX)\n", reg, offset) |
| 143 | + } |
| 144 | +} |
| 145 | + |
| 146 | +func emitWideKernel(f io.Writer, integerType integerType, width int) { |
| 147 | + wordCount := (width + 7) / 8 |
| 148 | + for word := 0; word < wordCount; word++ { |
| 149 | + wordOffset := word * 64 |
| 150 | + firstValue := wordOffset / width |
| 151 | + lastValue := min(7, (wordOffset+63)/width) |
| 152 | + for value := firstValue; value <= lastValue; value++ { |
| 153 | + reg := "R9" |
| 154 | + if value == firstValue { |
| 155 | + reg = "R8" |
| 156 | + } |
| 157 | + fmt.Fprintf(f, "\t%s %d(BX), %s\n", integerType.load, value*integerType.valueStride, reg) |
| 158 | + fmt.Fprintln(f, "\tANDQ R10, "+reg) |
| 159 | + shift := value*width - wordOffset |
| 160 | + switch { |
| 161 | + case shift > 0: |
| 162 | + fmt.Fprintf(f, "\tSHLQ $%d, %s\n", shift, reg) |
| 163 | + case shift < 0: |
| 164 | + fmt.Fprintf(f, "\tSHRQ $%d, %s\n", -shift, reg) |
| 165 | + } |
| 166 | + if value != firstValue { |
| 167 | + fmt.Fprintln(f, "\tORQ R9, R8") |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + offset := word * 8 |
| 172 | + if word < width/8 { |
| 173 | + fmt.Fprintf(f, "\tMOVQ R8, %d(AX)\n", offset) |
| 174 | + } else { |
| 175 | + emitTailStore(f, "R8", offset, width%8) |
| 176 | + } |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +func emitSmallKernel(f io.Writer, integerType integerType, width int) { |
| 181 | + regs := []string{"R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15"} |
| 182 | + for i, reg := range regs { |
| 183 | + emitValue(f, integerType, reg, i, i*width, width) |
| 184 | + } |
| 185 | + for stride := 1; stride < len(regs); stride *= 2 { |
| 186 | + for i := 0; i < len(regs); i += 2 * stride { |
| 187 | + fmt.Fprintf(f, "\tORQ %s, %s\n", regs[i+stride], regs[i]) |
| 188 | + } |
| 189 | + } |
| 190 | + emitTailStore(f, regs[0], 0, width) |
| 191 | +} |
| 192 | + |
| 193 | +func emitMediumKernel(f io.Writer, integerType integerType, width int) { |
| 194 | + low := []string{"R8", "R9", "R10", "R11"} |
| 195 | + high := []string{"R12", "R13", "R14", "R15"} |
| 196 | + for i, reg := range low { |
| 197 | + emitValue(f, integerType, reg, i, i*width, width) |
| 198 | + } |
| 199 | + for i, reg := range high { |
| 200 | + emitValue(f, integerType, reg, i+4, i*width, width) |
| 201 | + } |
| 202 | + emitFourValueReduction(f, low) |
| 203 | + emitFourValueReduction(f, high) |
| 204 | + |
| 205 | + shift := 4 * width |
| 206 | + fmt.Fprintln(f, "\tMOVQ R12, R9") |
| 207 | + fmt.Fprintf(f, "\tSHLQ $%d, R9\n", shift) |
| 208 | + fmt.Fprintln(f, "\tORQ R9, R8") |
| 209 | + fmt.Fprintf(f, "\tSHRQ $%d, R12\n", 64-shift) |
| 210 | + fmt.Fprintln(f, "\tMOVQ R8, (AX)") |
| 211 | + emitTailStore(f, "R12", 8, width-8) |
| 212 | +} |
| 213 | + |
| 214 | +func emitLargeKernel(f io.Writer, integerType integerType, width int) { |
| 215 | + groups := [][]string{ |
| 216 | + {"R8", "R9", "R10"}, |
| 217 | + {"R11", "R12", "R13"}, |
| 218 | + {"R14", "R15"}, |
| 219 | + } |
| 220 | + value := 0 |
| 221 | + for _, group := range groups { |
| 222 | + for i, reg := range group { |
| 223 | + emitValue(f, integerType, reg, value, i*width, width) |
| 224 | + value++ |
| 225 | + } |
| 226 | + for i := 1; i < len(group); i++ { |
| 227 | + fmt.Fprintf(f, "\tORQ %s, %s\n", group[i], group[0]) |
| 228 | + } |
| 229 | + } |
| 230 | + |
| 231 | + groupBits := 3 * width |
| 232 | + thirdOffset := 6*width - 64 |
| 233 | + fmt.Fprintln(f, "\tMOVQ R11, R9") |
| 234 | + fmt.Fprintf(f, "\tSHLQ $%d, R9\n", groupBits) |
| 235 | + fmt.Fprintln(f, "\tORQ R9, R8") |
| 236 | + fmt.Fprintf(f, "\tSHRQ $%d, R11\n", 64-groupBits) |
| 237 | + fmt.Fprintln(f, "\tMOVQ R14, R15") |
| 238 | + fmt.Fprintf(f, "\tSHLQ $%d, R15\n", thirdOffset) |
| 239 | + fmt.Fprintln(f, "\tORQ R15, R11") |
| 240 | + fmt.Fprintf(f, "\tSHRQ $%d, R14\n", 64-thirdOffset) |
| 241 | + fmt.Fprintln(f, "\tMOVQ R8, (AX)") |
| 242 | + fmt.Fprintln(f, "\tMOVQ R11, 8(AX)") |
| 243 | + emitTailStore(f, "R14", 16, width-16) |
| 244 | +} |
| 245 | + |
| 246 | +func emitPairKernel(f io.Writer, integerType integerType, width int) { |
| 247 | + pairs := [][2]string{ |
| 248 | + {"R8", "R9"}, |
| 249 | + {"R10", "R11"}, |
| 250 | + {"R12", "R13"}, |
| 251 | + {"R14", "R15"}, |
| 252 | + } |
| 253 | + for i, pair := range pairs { |
| 254 | + emitValue(f, integerType, pair[0], 2*i, 0, width) |
| 255 | + emitValue(f, integerType, pair[1], 2*i+1, width, width) |
| 256 | + fmt.Fprintf(f, "\tORQ %s, %s\n", pair[1], pair[0]) |
| 257 | + } |
| 258 | + |
| 259 | + pairBits := 2 * width |
| 260 | + fmt.Fprintln(f, "\tMOVQ R10, R9") |
| 261 | + fmt.Fprintf(f, "\tSHLQ $%d, R9\n", pairBits) |
| 262 | + fmt.Fprintln(f, "\tORQ R9, R8") |
| 263 | + fmt.Fprintln(f, "\tMOVQ R10, R11") |
| 264 | + fmt.Fprintf(f, "\tSHRQ $%d, R11\n", 64-pairBits) |
| 265 | + fmt.Fprintln(f, "\tMOVQ R12, R13") |
| 266 | + fmt.Fprintf(f, "\tSHLQ $%d, R13\n", 2*pairBits-64) |
| 267 | + fmt.Fprintln(f, "\tORQ R13, R11") |
| 268 | + fmt.Fprintf(f, "\tSHRQ $%d, R12\n", 128-2*pairBits) |
| 269 | + fmt.Fprintln(f, "\tMOVQ R14, R15") |
| 270 | + fmt.Fprintf(f, "\tSHLQ $%d, R15\n", 3*pairBits-128) |
| 271 | + fmt.Fprintln(f, "\tORQ R15, R12") |
| 272 | + |
| 273 | + fmt.Fprintln(f, "\tMOVQ R8, (AX)") |
| 274 | + fmt.Fprintln(f, "\tMOVQ R11, 8(AX)") |
| 275 | + if width <= 23 { |
| 276 | + emitTailStore(f, "R12", 16, width-16) |
| 277 | + return |
| 278 | + } |
| 279 | + fmt.Fprintf(f, "\tSHRQ $%d, R14\n", 192-3*pairBits) |
| 280 | + fmt.Fprintln(f, "\tMOVQ R12, 16(AX)") |
| 281 | + emitTailStore(f, "R14", 24, width-24) |
| 282 | +} |
| 283 | + |
| 284 | +func emitValue(f io.Writer, integerType integerType, reg string, index, shift, width int) { |
| 285 | + fmt.Fprintf(f, "\t%s %d(BX), %s\n", integerType.load, index*integerType.valueStride, reg) |
| 286 | + fmt.Fprintf(f, "\t%s $0x%x, %s\n", integerType.bitwiseAnd, (1<<width)-1, reg) |
| 287 | + if shift != 0 { |
| 288 | + fmt.Fprintf(f, "\tSHLQ $%d, %s\n", shift, reg) |
| 289 | + } |
| 290 | +} |
| 291 | + |
| 292 | +func emitFourValueReduction(f io.Writer, regs []string) { |
| 293 | + fmt.Fprintf(f, "\tORQ %s, %s\n", regs[1], regs[0]) |
| 294 | + fmt.Fprintf(f, "\tORQ %s, %s\n", regs[3], regs[2]) |
| 295 | + fmt.Fprintf(f, "\tORQ %s, %s\n", regs[2], regs[0]) |
| 296 | +} |
| 297 | + |
| 298 | +func emitTailStore(f io.Writer, reg string, offset, byteCount int) { |
| 299 | + if byteCount >= 4 { |
| 300 | + fmt.Fprintf(f, "\tMOVL %s, %d(AX)\n", reg, offset) |
| 301 | + offset += 4 |
| 302 | + byteCount -= 4 |
| 303 | + if byteCount > 0 { |
| 304 | + fmt.Fprintf(f, "\tSHRQ $32, %s\n", reg) |
| 305 | + } |
| 306 | + } |
| 307 | + if byteCount >= 2 { |
| 308 | + fmt.Fprintf(f, "\tMOVW %s, %d(AX)\n", reg, offset) |
| 309 | + offset += 2 |
| 310 | + byteCount -= 2 |
| 311 | + if byteCount > 0 { |
| 312 | + fmt.Fprintf(f, "\tSHRQ $16, %s\n", reg) |
| 313 | + } |
| 314 | + } |
| 315 | + if byteCount == 1 { |
| 316 | + fmt.Fprintf(f, "\tMOVB %s, %d(AX)\n", reg, offset) |
| 317 | + } |
| 318 | +} |
0 commit comments