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  }