gitee.com/quant1x/num@v0.3.2/asm/c2goasm/assembly.go (about) 1 /* 2 * Minio Cloud Storage, (C) 2017 Minio, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "fmt" 21 "regexp" 22 "strconv" 23 "strings" 24 "unicode" 25 ) 26 27 const originalStackPointer = 8 28 29 var registers = [...]string{"DI", "SI", "DX", "CX", "R8", "R9"} 30 var registersAdditional = [...]string{"R10", "R11", "R12", "R13", "R14", "R15", "AX", "BX"} 31 var regexpCall = regexp.MustCompile(`^\s*call\s*`) 32 var regexpPushInstr = regexp.MustCompile(`^\s*push\s*`) 33 var regexpPopInstr = regexp.MustCompile(`^\s*pop\s*`) 34 var regexpLabel = regexp.MustCompile(`^(\.?LBB.*?:)`) 35 var regexpJumpTableRef = regexp.MustCompile(`\[rip \+ (\.?LJTI[_0-9]*)\]\s*$`) 36 var regexpJumpWithLabel = regexp.MustCompile(`^(\s*j\w*)\s*(\.?LBB.*)`) 37 var regexpRbpLoadHigher = regexp.MustCompile(`\[rbp \+ ([0-9]+)\]`) 38 var regexpRbpLoadLower = regexp.MustCompile(`\[rbp - ([0-9]+)\]`) 39 var regexpStripComments = regexp.MustCompile(`\s*#?#\s.*$`) 40 41 // Write the prologue for the subroutine 42 func writeGoasmPrologue(sub Subroutine, stack Stack, arguments, returnValues []string) []string { 43 44 //if sub.name == "SimdSse2MedianFilterRhomb3x3" { 45 // fmt.Println("sub.name", sub.name) 46 // fmt.Println("sub.epilogue", sub.epilogue) 47 // fmt.Println("arguments", arguments) 48 // fmt.Println("returnValues", returnValues) 49 // fmt.Println("table.Name", sub.table.Name) 50 //} 51 52 var result []string 53 54 // Output definition of subroutine 55 result = append(result, fmt.Sprintf("TEXT ·_%s(SB), $%d-%d", sub.name, stack.GolangLocalStackFrameSize(), 56 getTotalSizeOfArgumentsAndReturnValues(0, len(arguments)-1, returnValues)), "") 57 58 // Load Golang arguments into registers 59 for iarg, arg := range arguments { 60 61 if iarg < len(registers) { 62 // Load initial arguments (up to 6) in corresponding registers 63 result = append(result, fmt.Sprintf(" MOVQ %s+%d(FP), %s", arg, iarg*8, registers[iarg])) 64 } else if iarg-len(registers) < len(registersAdditional) { 65 // Load following arguments into additional registers 66 result = append(result, fmt.Sprintf(" MOVQ %s+%d(FP), %s", arg, iarg*8, registersAdditional[iarg-len(registers)])) 67 } else { 68 panic("Trying to pass in too many arguments") 69 } 70 } 71 72 // Setup the stack pointer for the C code 73 if sub.epilogue.AlignedStack { 74 // Align stack pointer to next multiple of alignment space 75 result = append(result, fmt.Sprintf(" MOVQ SP, BP")) 76 result = append(result, fmt.Sprintf(" ADDQ $%d, SP", stack.StackPointerOffsetForC() /*sub.epilogue.AlignValue*/)) 77 result = append(result, fmt.Sprintf(" ANDQ $-%d, SP", sub.epilogue.AlignValue)) 78 79 // Save original stack pointer right below newly aligned stack pointer 80 result = append(result, fmt.Sprintf(" MOVQ BP, %d(SP)", stack.OffsetForSavedSP())) // Save original SP 81 82 } else if stack.StackPointerOffsetForC() != 0 { // sub.epilogue.getStackSpace(len(arguments)) != 0 { 83 // Create stack space as needed 84 result = append(result, fmt.Sprintf(" ADDQ $%d, SP", stack.StackPointerOffsetForC() /*sub.epilogue.getFreeSpaceAtBottom())*/)) 85 } 86 87 // Save Golang arguments beyond 6 onto stack 88 for iarg := len(arguments) - 1; iarg-len(registers) >= 0; iarg-- { 89 result = append(result, fmt.Sprintf(" MOVQ %s, %d(SP)", registersAdditional[iarg-len(registers)], stack.OffsetForGoArg(iarg))) 90 } 91 92 // Setup base pointer for loading constants 93 if sub.table.isPresent() { 94 result = append(result, fmt.Sprintf(" LEAQ %s<>(SB), BP", sub.table.Name)) 95 } 96 97 return append(result, ``) 98 } 99 100 func writeGoasmBody(sub Subroutine, stack Stack, stackArgs StackArgs, arguments, returnValues []string) ([]string, error) { 101 102 var result []string 103 104 for iline, line := range sub.body { 105 106 // If part of epilogue 107 if iline >= sub.epilogue.Start && iline < sub.epilogue.End { 108 109 // Instead of last line, output go assembly epilogue 110 if iline == sub.epilogue.End-1 { 111 result = append(result, writeGoasmEpilogue(sub, stack, arguments, returnValues)...) 112 } 113 continue 114 } 115 116 // Remove ## comments 117 var skipLine bool 118 line, skipLine = stripComments(line) 119 if skipLine { 120 continue 121 } 122 123 // Skip lines with aligns 124 if strings.Contains(line, ".align") || strings.Contains(line, ".p2align") { 125 continue 126 } 127 128 line, _ = fixLabels(line) 129 line, _, _ = upperCaseJumps(line) 130 line, _ = upperCaseCalls(line) 131 132 fields := strings.Fields(line) 133 // Test for any non-jmp instruction (lower case mnemonic) 134 if len(fields) > 0 && !strings.Contains(fields[0], ":") && isLower(fields[0]) { 135 // prepend line with comment for subsequent asm2plan9s assembly 136 line = " // " + strings.TrimSpace(line) 137 } 138 139 line = removeUndefined(line, "ptr") 140 line = removeUndefined(line, "# NOREX") 141 142 // https://github.com/vertis/objconv/blob/master/src/disasm2.cpp 143 line = replaceUndefined(line, "xmmword", "oword") 144 line = replaceUndefined(line, "ymmword", "yword") 145 146 line = fixShiftInstructions(line) 147 line = fixMovabsInstructions(line) 148 if sub.table.isPresent() { 149 line = fixPicLabels(line, sub.table) 150 } 151 152 line = fixRbpPlusLoad(line, stackArgs, stack) 153 154 detectRbpMinusMemoryAccess(line) 155 detectJumpTable(line) 156 detectPushInstruction(line) 157 detectPopInstruction(line) 158 159 result = append(result, line) 160 } 161 162 return result, nil 163 } 164 165 // Write the epilogue for the subroutine 166 func writeGoasmEpilogue(sub Subroutine, stack Stack, arguments, returnValues []string) []string { 167 168 var result []string 169 170 // Restore the stack pointer 171 if sub.epilogue.AlignedStack { 172 // For an aligned stack, restore the stack pointer from the stack itself 173 result = append(result, fmt.Sprintf(" MOVQ %d(SP), SP", stack.OffsetForSavedSP())) 174 } else if stack.StackPointerOffsetForC() != 0 { 175 // For an unaligned stack, reverse addition in order restore the stack pointer 176 result = append(result, fmt.Sprintf(" SUBQ $%d, SP", stack.StackPointerOffsetForC())) 177 } 178 179 // Clear upper half of YMM register, if so done in the original code 180 if sub.epilogue.VZeroUpper { 181 result = append(result, " VZEROUPPER") 182 } 183 184 if len(returnValues) == 1 { 185 // Store return value of subroutine 186 result = append(result, fmt.Sprintf(" MOVQ AX, %s+%d(FP)", returnValues[0], 187 getTotalSizeOfArgumentsAndReturnValues(0, len(arguments)-1, returnValues)-8)) 188 } else if len(returnValues) > 1 { 189 panic(fmt.Sprintf("Fix multiple return values: %s", returnValues)) 190 } 191 192 // Finally, return out of the subroutine 193 result = append(result, " RET") 194 195 return result 196 } 197 198 func scanBodyForCalls(sub Subroutine) uint { 199 200 stackSize := uint(0) 201 202 for _, line := range sub.body { 203 204 _, size := upperCaseCalls(line) 205 206 if stackSize < size { 207 stackSize = size 208 } 209 } 210 211 return stackSize 212 } 213 214 // Strip comments from assembly lines 215 func stripComments(line string) (result string, skipLine bool) { 216 217 if match := regexpStripComments.FindStringSubmatch(line); len(match) > 0 { 218 line = line[:len(line)-len(match[0])] 219 if line == "" { 220 return "", true 221 } 222 } 223 return line, false 224 } 225 226 // Remove leading `.` from labels 227 func fixLabels(line string) (string, string) { 228 229 label := "" 230 231 if match := regexpLabel.FindStringSubmatch(line); len(match) > 0 { 232 label = strings.Replace(match[1], ".", "", 1) 233 line = label 234 label = strings.Replace(label, ":", "", 1) 235 } 236 237 return line, label 238 } 239 240 // Make jmps uppercase 241 func upperCaseJumps(line string) (string, string, string) { 242 243 instruction, label := "", "" 244 245 if match := regexpJumpWithLabel.FindStringSubmatch(line); len(match) > 1 { 246 // make jmp statement uppercase 247 instruction = strings.ToUpper(match[1]) 248 label = strings.Replace(match[2], ".", "", 1) 249 line = instruction + " " + label 250 251 } 252 253 return line, strings.TrimSpace(instruction), label 254 } 255 256 // Make calls uppercase 257 func upperCaseCalls(line string) (string, uint) { 258 259 // TODO: Make determination of required stack size more sophisticated 260 stackSize := uint(0) 261 262 // Make 'call' instructions uppercase 263 if match := regexpCall.FindStringSubmatch(line); len(match) > 0 { 264 parts := strings.SplitN(line, `call`, 2) 265 fname := strings.TrimSpace(parts[1]) 266 267 // replace c stdlib functions with equivalents 268 if fname == "_memcpy" || fname == "memcpy@PLT" { // (Procedure Linkage Table) 269 parts[1] = "clib·_memcpy(SB)" 270 stackSize = 64 271 } else if fname == "_memset" || fname == "memset@PLT" { // (Procedure Linkage Table) 272 parts[1] = "clib·_memset(SB)" 273 stackSize = 64 274 } else if fname == "_floor" || fname == "floor@PLT" { // (Procedure Linkage Table) 275 parts[1] = "clib·_floor(SB)" 276 stackSize = 64 277 } else if fname == "___bzero" { 278 parts[1] = "clib·_bzero(SB)" 279 stackSize = 64 280 } 281 line = parts[0] + "CALL " + strings.TrimSpace(parts[1]) 282 } 283 284 return line, stackSize 285 } 286 287 func isLower(str string) bool { 288 289 for _, r := range str { 290 return unicode.IsLower(r) 291 } 292 return false 293 } 294 295 func removeUndefined(line, undef string) string { 296 297 if parts := strings.SplitN(line, undef, 2); len(parts) > 1 { 298 line = parts[0] + strings.TrimSpace(parts[1]) 299 } 300 return line 301 } 302 303 func replaceUndefined(line, undef, repl string) string { 304 305 if parts := strings.SplitN(line, undef, 2); len(parts) > 1 { 306 line = parts[0] + repl + parts[1] 307 } 308 return line 309 } 310 311 // fix Position Independent Labels 312 func fixPicLabels(line string, table Table) string { 313 314 if strings.Contains(line, "[rip + ") { 315 parts := strings.SplitN(line, "[rip + ", 2) 316 label := parts[1][:len(parts[1])-1] 317 318 i := -1 319 var l Label 320 for i, l = range table.Labels { 321 if l.Name == label { 322 line = parts[0] + fmt.Sprintf("%d[rbp] /* [rip + %s */", l.Offset, parts[1]) 323 break 324 } 325 } 326 if i == len(table.Labels) { 327 panic(fmt.Sprintf("Failed to find label to replace of position independent code: %s", label)) 328 } 329 } 330 331 return line 332 } 333 334 func fixShiftNoArgument(line, ins string) string { 335 336 if strings.Contains(line, ins) { 337 parts := strings.SplitN(line, ins, 2) 338 args := strings.SplitN(parts[1], ",", 2) 339 if len(args) == 1 { 340 line += ", 1" 341 } 342 } 343 344 return line 345 } 346 347 func fixShiftInstructions(line string) string { 348 349 line = fixShiftNoArgument(line, "shr") 350 line = fixShiftNoArgument(line, "sar") 351 352 return line 353 } 354 355 func fixMovabsInstructions(line string) string { 356 357 if strings.Contains(line, "movabs") { 358 parts := strings.SplitN(line, "movabs", 2) 359 line = parts[0] + "mov" + parts[1] 360 } 361 362 return line 363 } 364 365 // Fix loads in the form of '[rbp + constant]' 366 // These are load instructions for stack-based arguments that occur after the first 6 arguments 367 // Remap to stack pointer 368 func fixRbpPlusLoad(line string, stackArgs StackArgs, stack Stack) string { 369 370 if match := regexpRbpLoadHigher.FindStringSubmatch(line); len(match) > 1 { 371 offset, _ := strconv.Atoi(match[1]) 372 // TODO: Get proper offset for non 64-bit arguments 373 iarg := len(registers) + (offset-stackArgs.OffsetToFirst)/8 374 parts := strings.SplitN(line, match[0], 2) 375 line = parts[0] + fmt.Sprintf("%d[rsp]%s /* %s */", stack.OffsetForGoArg(iarg), parts[1], match[0]) 376 } 377 378 return line 379 } 380 381 // Detect memory accesses in the form of '[rbp - constant]' 382 func detectRbpMinusMemoryAccess(line string) { 383 384 if match := regexpRbpLoadLower.FindStringSubmatch(line); len(match) > 1 { 385 386 panic(fmt.Sprintf("Not expected to find [rbp -] based loads: %s\n\nDid you specify `-mno-red-zone`?\n\n", line)) 387 } 388 } 389 390 // Detect jump tables 391 func detectJumpTable(line string) { 392 393 if match := regexpJumpTableRef.FindStringSubmatch(line); len(match) > 0 { 394 panic(fmt.Sprintf("Jump table detected: %s\n\nCircumvent using '-fno-jump-tables', see 'clang -cc1 -help' (version 3.9+)\n\n", match[1])) 395 } 396 } 397 398 // Detect push instructions 399 func detectPushInstruction(line string) { 400 401 if match := regexpPushInstr.FindStringSubmatch(line); len(match) > 0 { 402 panic(fmt.Sprintf("push instruction detected: %s\n\nCannot modify `rsp` in body of assembly\n\n", match[1])) 403 } 404 } 405 406 // Detect pop instructions 407 func detectPopInstruction(line string) { 408 409 if match := regexpPopInstr.FindStringSubmatch(line); len(match) > 0 { 410 panic(fmt.Sprintf("pop instruction detected: %s\n\nCannot modify `rsp` in body of assembly\n\n", match[1])) 411 } 412 }