modernc.org/qbe@v0.0.9/qbe.go (about) 1 // Copyright 2021 The QBE Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:generate stringer -output stringer.go -linecomment -type=Ch,vmInst,vmOperandKind,vmType 6 7 // Package qbe deals with the QBE intermediate language 8 // 9 // QBE intermediate language introduction: 10 // 11 // https://c9x.me/compile/ 12 // 13 // QBE reference: 14 // 15 // https://c9x.me/compile/doc/il.html 16 // 17 // QBE vs LLVM: 18 // 19 // https://c9x.me/compile/doc/llvm.html 20 // 21 // Build status 22 // 23 // Available at https://modern-c.appspot.com/-/builder/?importpath=modernc.org%2fqbe 24 // 25 // Limitations 26 // 27 // The environmental function parameter/argument, as defined at 28 // 29 // https://c9x.me/compile/doc/il.html#Functions 30 // 31 // is not supported. 32 package qbe // import "modernc.org/qbe" 33 34 //TODO https://wasmer.io/ ? 35 36 import ( 37 "bufio" 38 "fmt" 39 "io" 40 "io/ioutil" 41 "os" 42 "os/exec" 43 "path/filepath" 44 "runtime" 45 "strings" 46 47 "modernc.org/opt" 48 ) 49 50 const ( 51 // Reserved ABI type name that maps to C va_list. For example 52 // 53 // A C function 54 // 55 // int vprintf(const char *format, va_list ap); 56 // 57 // can become a QBE function 58 // 59 // export function w vprintf(l format, :__qbe_va_list ap) { ... } 60 VaList = "__qbe_va_list" 61 62 // Reserved ABI type name that maps to C *va_list. For example 63 // 64 // A C function 65 // 66 // void foo(va_list *ap); 67 // 68 // can become a QBE function 69 // 70 // export function foo(:__qbe_va_listp ap) { ... } 71 VaListPtr = "__qbe_va_listp" 72 73 version = "0.0.8-20210725" 74 ) 75 76 var ( 77 _ = trc //TODOOK 78 ) 79 80 // VaInfo describes a va_list 81 type VaInfo struct { 82 Size int // sizeof va 83 ElemSize int // sizeof *va or 0 if N/A 84 85 // IsStructArray reports whether va_list if defined as, for example 86 // 87 // typedef struct { 88 // unsigned int gp_offset; 89 // unsigned int fp_offset; 90 // void *overflow_arg_area; 91 // void *reg_save_area; 92 // } __builtin_va_list[1]; 93 IsStructArray bool 94 // IsPointer reports whether va_list if defined as, for example 95 // 96 // typedef char *__builtin_va_list; 97 IsPointer bool 98 // IsStruct reports whether va_list if defined as, for example 99 // 100 // typedef struct { 101 // void *__ap; 102 // } __builtin_va_list; 103 IsStruct bool 104 } 105 106 // VaInfoFor returns a VaInfo for os/arch or an error, if any. 107 func VaInfoFor(os, arch string) (*VaInfo, error) { 108 switch os { 109 case "darwin": 110 switch arch { 111 case "amd64": 112 return &VaInfo{ 113 Size: 24, 114 ElemSize: 24, 115 IsStructArray: true, 116 }, nil 117 } 118 case "freebsd", "netbsd": 119 switch arch { 120 case "amd64": 121 return &VaInfo{ 122 Size: 24, 123 ElemSize: 24, 124 IsStructArray: true, 125 }, nil 126 } 127 case "linux": 128 switch arch { 129 case "amd64": 130 return &VaInfo{ 131 Size: 24, 132 ElemSize: 24, 133 IsStructArray: true, 134 }, nil 135 case "arm64": 136 return &VaInfo{ 137 Size: 32, 138 ElemSize: 0, // N/A 139 IsStruct: true, 140 }, nil 141 case "arm": 142 return &VaInfo{ 143 Size: 4, 144 ElemSize: 0, // N/A 145 IsStruct: true, 146 }, nil 147 case "386": 148 return &VaInfo{ 149 Size: 4, 150 ElemSize: 1, 151 IsPointer: true, 152 }, nil 153 case "s390x": 154 return &VaInfo{ 155 Size: 32, 156 ElemSize: 32, 157 IsStructArray: true, 158 }, nil 159 } 160 case "windows": 161 switch arch { 162 case "amd64": 163 return &VaInfo{ 164 Size: 8, 165 ElemSize: 1, 166 IsPointer: true, 167 }, nil 168 } 169 } 170 171 return nil, fmt.Errorf("unknown/usupported target %s/%s", os, arch) 172 } 173 174 // Version reports the version of the qbe package. 175 func Version() string { return fmt.Sprintf("%v %v/%v", version, runtime.GOOS, runtime.GOARCH) } 176 177 // Task represents a compilation job. 178 type Task struct { 179 O string // -O argument. Read only. 180 arch string 181 args []string // As passed to NewTask. 182 cc string 183 name string // As passed to NewTask. 184 o string // -o argument. 185 os string 186 ptr Type 187 stderr io.Writer 188 stdout io.Writer 189 190 c bool // -c 191 keepTmp bool // -keep-tmp 192 } 193 194 // NewTask returns a newly created Task. 195 func NewTask(name string, args []string, stdout, stderr io.Writer) *Task { 196 return &Task{ 197 arch: env("TARGET_ARCH", env("GOARCH", runtime.GOARCH)), 198 args: args, 199 cc: env("QBEC_CC", env("CC", "gcc")), 200 os: env("TARGET_OS", env("GOOS", runtime.GOOS)), 201 name: name, 202 stderr: stderr, 203 stdout: stdout, 204 } 205 } 206 207 // Main executes task. 208 func (t *Task) Main() (err error) { 209 switch t.arch { 210 case "386", "arm": 211 t.ptr = VoidPointer{w} 212 case "amd64", "arm64", "s390x": 213 t.ptr = VoidPointer{l} 214 default: 215 return errorf("unknown/unsupported architecture: %q", t.arch) 216 } 217 218 cc, err := exec.LookPath(t.cc) 219 if err != nil { 220 return errorf("%s: cannot lookup path for %s: %v", t.name, t.cc, err) 221 } 222 223 t.cc = cc 224 stop := false 225 opts := opt.NewSet() 226 opts.Arg("O", true, func(opt, arg string) error { t.O = arg; return nil }) 227 opts.Arg("o", false, func(opt, arg string) error { t.o = arg; return nil }) 228 opts.Opt("c", func(opt string) error { t.c = true; return nil }) 229 opts.Opt("keep-tmp", func(opt string) error { t.keepTmp = true; return nil }) 230 opts.Opt("version", func(opt string) error { 231 fmt.Fprintln(t.stderr, Version()) 232 stop = true 233 return nil 234 }) 235 opts.Opt("-version", func(opt string) error { 236 fmt.Fprintln(t.stderr, Version()) 237 stop = true 238 return nil 239 }) 240 241 var in, replace []string 242 if err := opts.Parse(t.args, func(s string) error { 243 if strings.HasPrefix(s, "-") { 244 return nil 245 } 246 247 if strings.HasSuffix(s, ".qbe") { 248 in = append(in, s) 249 } 250 251 return nil 252 }); err != nil { 253 return errorf("%s: %v", t.name, err) 254 } 255 if stop { 256 return nil 257 } 258 259 if len(in) != 0 { 260 produceC := false 261 if strings.HasSuffix(t.o, ".c") { 262 if len(in) != 1 { 263 return errorf("%s: output set to a C file with multiple QBE input files", t.name) 264 } 265 266 produceC = true 267 } 268 269 var tmpDir, cPath string 270 if !produceC { 271 tmpDir, err = ioutil.TempDir("", "qbec-") 272 if err != nil { 273 return errorf("%s: cannot create temporary directory: %v", t.name, err) 274 } 275 276 defer func() { 277 if t.keepTmp { 278 fmt.Fprintf(os.Stderr, "keeping temporary directory %s\n", tmpDir) 279 return 280 } 281 282 os.RemoveAll(tmpDir) 283 }() 284 } 285 286 cNames := map[string]struct{}{} 287 for i, v := range in { 288 baseName := filepath.Base(v) 289 switch { 290 case produceC: 291 cPath = t.o 292 default: 293 cName := baseName[:len(baseName)-len(".qbe")] + ".c" 294 dir := tmpDir 295 if _, ok := cNames[cName]; ok { 296 dir = filepath.Join(tmpDir, fmt.Sprint(i)) 297 if err := os.Mkdir(dir, 0700); err != nil { 298 return errorf("mkdir(%s): %v", dir, err) 299 } 300 } 301 cNames[cName] = struct{}{} 302 cPath = filepath.Join(dir, cName) 303 } 304 replace = append(replace, cPath) 305 b, err := ioutil.ReadFile(v) 306 if err != nil { 307 return errorf("%s: cannot open input file: %v", t.name, err) 308 } 309 310 cst, err := Parse(b, v, false) 311 if err != nil { 312 return errorf("%s: %v", t.name, err) 313 } 314 315 ast, err := cst.AST(t.os, t.arch) 316 if err != nil { 317 return errorf("%s: %v", t.name, err) 318 } 319 320 f, err := os.Create(cPath) 321 if err != nil { 322 return errorf("%s: cannot create file: %v", t.name, err) 323 } 324 325 w := bufio.NewWriter(f) 326 if err := ast.C(w, t.headers(), t.os, t.arch); err != nil { 327 return errorf("%s: %v", t.name, err) 328 } 329 330 if err := ast.Warning(); err != nil { 331 fmt.Fprintf(t.stderr, "%s\n", err) 332 } 333 334 if err := w.Flush(); err != nil { 335 return errorf("%s: cannot flush file: %v", t.name, err) 336 } 337 338 if err := f.Close(); err != nil { 339 return errorf("%s: cannot close file: %v", t.name, err) 340 } 341 342 if produceC { 343 return nil 344 } 345 } 346 } 347 var args []string 348 if t.O == "" { 349 args = append(args, "-O1") 350 } 351 for _, v := range t.args { 352 if v == "-keep-tmp" { 353 continue 354 } 355 356 if len(in) != 0 && in[0] == v { 357 v = replace[0] 358 in = in[1:] 359 replace = replace[1:] 360 } 361 args = append(args, v) 362 } 363 cmd := exec.Command(t.cc, args...) 364 cmd.Stdout = t.stdout 365 cmd.Stderr = t.stderr 366 if err = cmd.Run(); err != nil { 367 wd, err2 := os.Getwd() 368 err = errorf("%s: executing %s %v (in %v, err %v): %v", t.name, t.cc, args, wd, err2, err) 369 } 370 return err 371 } 372 373 func (t *Task) headers() string { 374 switch t.os { 375 case "windows": 376 // https://stackoverflow.com/a/58286937 377 return ` 378 #include <malloc.h> 379 #include <stdarg.h> 380 #include <stdint.h> 381 ` 382 default: 383 return ` 384 #include <alloca.h> 385 #include <stdarg.h> 386 #include <stdint.h> 387 ` 388 } 389 } 390 391 func env(name, deflt string) (r string) { 392 if s := os.Getenv(name); s != "" { 393 return s 394 } 395 396 return deflt 397 }