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  }