gitee.com/quant1x/num@v0.3.2/asm/c2goasm/c2goasm.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 "bufio" 21 "encoding/hex" 22 "flag" 23 "fmt" 24 "log" 25 "os" 26 "os/exec" 27 "regexp" 28 "strings" 29 ) 30 31 var ( 32 assembleFlag = flag.Bool("a", false, "Immediately invoke asm2plan9s") 33 stripFlag = flag.Bool("s", false, "Strip comments") 34 compactFlag = flag.Bool("c", false, "Compact byte codes") 35 formatFlag = flag.Bool("f", false, "Format using asmfmt") 36 targetFlag = flag.String("t", "x86", "Target machine of input code") 37 ) 38 39 // readLines reads a whole file into memory 40 // and returns a slice of its lines. 41 func readLines(path string) ([]string, error) { 42 file, err := os.Open(path) 43 if err != nil { 44 return nil, err 45 } 46 defer file.Close() 47 48 var lines []string 49 scanner := bufio.NewScanner(file) 50 for scanner.Scan() { 51 lines = append(lines, scanner.Text()) 52 } 53 return lines, scanner.Err() 54 } 55 56 // writeLines writes the lines to the given file. 57 func writeLines(lines []string, path string, header bool) error { 58 file, err := os.Create(path) 59 if err != nil { 60 return err 61 } 62 defer file.Close() 63 64 w := bufio.NewWriter(file) 65 if header { 66 fmt.Fprintln(w, "//+build !noasm !appengine") 67 fmt.Fprintln(w, "// AUTO-GENERATED BY C2GOASM -- DO NOT EDIT") 68 fmt.Fprintln(w, "") 69 } 70 for _, line := range lines { 71 fmt.Fprintln(w, line) 72 } 73 return w.Flush() 74 } 75 76 func process(assembly []string, goCompanionFile string) ([]string, error) { 77 78 // Split out the assembly source into subroutines 79 subroutines := segmentSource(assembly) 80 tables := segmentConstTables(assembly) 81 82 var result []string 83 84 // Iterate over all subroutines 85 for isubroutine, sub := range subroutines { 86 87 golangArgs, golangReturns := parseCompanionFile(goCompanionFile, sub.name) 88 stackArgs := argumentsOnStack(sub.body) 89 if len(golangArgs) > 6 && len(golangArgs)-6 < stackArgs.Number { 90 panic(fmt.Sprintf("Found too few arguments on stack (%d) but needed %d", len(golangArgs)-6, stackArgs.Number)) 91 } 92 93 // Check for constants table 94 if table := getCorrespondingTable(sub.body, tables); table.isPresent() { 95 96 // Output constants table 97 result = append(result, strings.Split(table.Constants, "\n")...) 98 result = append(result, "") // append empty line 99 100 sub.table = table 101 } 102 103 // Create object to get offsets for stack pointer 104 stack := NewStack(sub.epilogue, len(golangArgs), scanBodyForCalls(sub)) 105 106 // Write header for subroutine in go assembly 107 result = append(result, writeGoasmPrologue(sub, stack, golangArgs, golangReturns)...) 108 109 // Write body of code 110 assembly, err := writeGoasmBody(sub, stack, stackArgs, golangArgs, golangReturns) 111 if err != nil { 112 panic(fmt.Sprintf("writeGoasmBody: %v", err)) 113 } 114 result = append(result, assembly...) 115 116 if isubroutine < len(subroutines)-1 { 117 // Empty lines before next subroutine 118 result = append(result, "\n", "\n") 119 } 120 } 121 122 return result, nil 123 } 124 125 func stripGoasmComments(file string) { 126 127 lines, err := readLines(file) 128 if err != nil { 129 log.Fatalf("readLines: %s", err) 130 } 131 132 for i, l := range lines { 133 if strings.Contains(l, "LONG") || strings.Contains(l, "WORD") || strings.Contains(l, "BYTE") { 134 opcode := strings.TrimSpace(strings.SplitN(l, "//", 2)[0]) 135 lines[i] = strings.SplitN(l, opcode, 2)[0] + opcode 136 } 137 } 138 139 err = writeLines(lines, file, false) 140 if err != nil { 141 log.Fatalf("writeLines: %s", err) 142 } 143 } 144 145 func reverseBytes(hex string) string { 146 147 result := "" 148 for i := len(hex) - 2; i >= 0; i -= 2 { 149 result = result + hex[i:i+2] 150 } 151 return result 152 } 153 154 func compactArray(opcodes []byte) []string { 155 156 var result []string 157 158 dst := make([]byte, hex.EncodedLen(len(opcodes))) 159 hex.Encode(dst, opcodes) 160 161 q := 0 162 for ; q+31 < len(dst); q += 32 { 163 result = append(result, fmt.Sprintf(" QUAD $0x%s; QUAD $0x%s", reverseBytes(string(dst[q:q+16])), reverseBytes(string(dst[q+16:q+32])))) 164 } 165 for ; q+15 < len(dst); q += 16 { 166 result = append(result, fmt.Sprintf(" QUAD $0x%s", reverseBytes(string(dst[q:q+16])))) 167 } 168 if q < len(dst) { 169 last := "" 170 l := 0 171 if q+7 < len(dst) { 172 last += fmt.Sprintf("LONG $0x%s", reverseBytes(string(dst[q:q+8]))) 173 l = 8 174 } 175 w := 0 176 if q+l+3 < len(dst) { 177 if len(last) > 0 { 178 last = last + "; " 179 } 180 last += fmt.Sprintf("WORD $0x%s", reverseBytes(string(dst[q+l:q+l+4]))) 181 w = 4 182 } 183 if q+l+w+1 < len(dst) { 184 if len(last) > 0 { 185 last = last + "; " 186 } 187 last += fmt.Sprintf("BYTE $0x%s", dst[q+l+w:q+l+w+2]) 188 } 189 result = append(result, " "+last) 190 } 191 192 return result 193 } 194 195 func compactOpcodes(file string) { 196 197 lines, err := readLines(file) 198 if err != nil { 199 log.Fatalf("readLines: %s", err) 200 } 201 202 var result []string 203 204 opcodes := make([]byte, 0, 1000) 205 206 hexMatch := regexp.MustCompile(`(\$0x[0-9a-f]+)`) 207 208 for _, l := range lines { 209 if strings.Contains(l, "LONG") || strings.Contains(l, "WORD") || strings.Contains(l, "BYTE") { 210 match := hexMatch.FindAllStringSubmatch(l, -1) 211 for _, m := range match { 212 dst := make([]byte, hex.DecodedLen(len(m[0][3:]))) 213 _, err := hex.Decode(dst, []byte(m[0][3:])) 214 if err != nil { 215 log.Fatal(err) 216 } 217 for i := len(dst) - 1; i >= 0; i -= 1 { // append starting with lowest byte first 218 opcodes = append(opcodes, dst[i:i+1]...) 219 } 220 } 221 } else { 222 223 if len(opcodes) != 0 { 224 result = append(result, compactArray(opcodes)...) 225 opcodes = opcodes[:0] 226 } 227 228 result = append(result, l) 229 } 230 } 231 232 err = writeLines(result, file, false) 233 if err != nil { 234 log.Fatalf("writeLines: %s", err) 235 } 236 } 237 238 func main() { 239 240 flag.Parse() 241 242 if flag.NArg() < 2 { 243 fmt.Printf("error: not enough input files specified\n\n") 244 fmt.Println("usage: c2goasm /path/to/c-project/build/SomeGreatCode.cpp.s SomeGreatCode_amd64.s") 245 return 246 } 247 assemblyFile := flag.Arg(1) 248 if !strings.HasSuffix(assemblyFile, ".s") { 249 fmt.Printf("error: second parameter must have '.s' extension\n") 250 return 251 } 252 253 goCompanion := assemblyFile[:len(assemblyFile)-2] + ".go" 254 if _, err := os.Stat(goCompanion); os.IsNotExist(err) { 255 fmt.Printf("error: companion '.go' file is missing for %s\n", flag.Arg(1)) 256 return 257 } 258 259 fmt.Println("Processing", flag.Arg(0)) 260 lines, err := readLines(flag.Arg(0)) 261 if err != nil { 262 log.Fatalf("readLines: %s", err) 263 } 264 265 result, err := process(lines, goCompanion) 266 if err != nil { 267 fmt.Print(err) 268 os.Exit(-1) 269 } 270 271 err = writeLines(result, assemblyFile, true) 272 if err != nil { 273 log.Fatalf("writeLines: %s", err) 274 } 275 276 if *assembleFlag { 277 fmt.Println("Invoking asm2plan9s on", assemblyFile) 278 cmd := exec.Command("asm2plan9s", assemblyFile) 279 _, err := cmd.CombinedOutput() 280 if err != nil { 281 log.Fatalf("asm2plan9s: %v", err) 282 } 283 } 284 285 if *stripFlag { 286 stripGoasmComments(assemblyFile) 287 } 288 289 if *compactFlag { 290 compactOpcodes(assemblyFile) 291 } 292 293 if *formatFlag { 294 cmd := exec.Command("asmfmt", "-w", assemblyFile) 295 _, err := cmd.CombinedOutput() 296 if err != nil { 297 log.Fatalf("asmfmt: %v", err) 298 } 299 } 300 }