bootstrap.go raw

   1  package builder
   2  
   3  import (
   4  	"fmt"
   5  	"go/types"
   6  	"os"
   7  	"path/filepath"
   8  	"strings"
   9  	"sync"
  10  	"unsafe"
  11  
  12  	"github.com/ebitengine/purego"
  13  	"moxie/loader"
  14  	"tinygo.org/x/go-llvm"
  15  )
  16  
  17  var (
  18  	bootstrapOnce        sync.Once
  19  	bootstrapLib         uintptr
  20  	bootstrapCompile     func(uintptr, int32, uintptr, int32, uintptr, int32) int32
  21  	bootstrapIRLen       func(int32) int32
  22  	bootstrapIRCopy      func(int32, uintptr, int32) int32
  23  	bootstrapIRFree      func(int32)
  24  	bootstrapRegPkg      func(uintptr, int32, uintptr, int32)
  25  	bootstrapRegFunc     func(uintptr, int32, uintptr, int32, uintptr, int32)
  26  	bootstrapRegVar      func(uintptr, int32, uintptr, int32, uintptr, int32)
  27  	bootstrapClearImport func()
  28  	bootstrapErr         error
  29  )
  30  
  31  func initBootstrap() {
  32  	bootstrapOnce.Do(func() {
  33  		soPath := os.Getenv("MOXIE_COMPILE_SO")
  34  		if soPath == "" {
  35  			root := os.Getenv("MOXIEROOT")
  36  			if root == "" {
  37  				root, _ = os.Executable()
  38  				root = filepath.Dir(root)
  39  			}
  40  			soPath = filepath.Join(root, "bootstrap", "compile.so")
  41  		}
  42  		if _, err := os.Stat(soPath); err != nil {
  43  			bootstrapErr = err
  44  			return
  45  		}
  46  		lib, err := purego.Dlopen(soPath, purego.RTLD_NOW|purego.RTLD_GLOBAL)
  47  		if err != nil {
  48  			bootstrapErr = fmt.Errorf("dlopen %s: %w", soPath, err)
  49  			return
  50  		}
  51  		bootstrapLib = lib
  52  		purego.RegisterLibFunc(&bootstrapCompile, lib, "moxie_compile_to_ir")
  53  		purego.RegisterLibFunc(&bootstrapIRLen, lib, "moxie_compile_ir_len")
  54  		purego.RegisterLibFunc(&bootstrapIRCopy, lib, "moxie_compile_ir_copy")
  55  		purego.RegisterLibFunc(&bootstrapIRFree, lib, "moxie_compile_ir_free")
  56  		purego.RegisterLibFunc(&bootstrapRegPkg, lib, "moxie_register_package")
  57  		purego.RegisterLibFunc(&bootstrapRegFunc, lib, "moxie_register_func")
  58  		purego.RegisterLibFunc(&bootstrapRegVar, lib, "moxie_register_var")
  59  		purego.RegisterLibFunc(&bootstrapClearImport, lib, "moxie_clear_imports")
  60  	})
  61  }
  62  
  63  func bootstrapAvailable() bool {
  64  	initBootstrap()
  65  	return bootstrapErr == nil && bootstrapLib != 0
  66  }
  67  
  68  func bootstrapRegisterStr(fn func(uintptr, int32, uintptr, int32), a, b string) {
  69  	ab := []byte(a)
  70  	bb := []byte(b)
  71  	fn(uintptr(unsafe.Pointer(&ab[0])), int32(len(ab)), uintptr(unsafe.Pointer(&bb[0])), int32(len(bb)))
  72  }
  73  
  74  func bootstrapRegisterStr3(fn func(uintptr, int32, uintptr, int32, uintptr, int32), a, b, c string) {
  75  	ab := []byte(a)
  76  	bb := []byte(b)
  77  	cb := []byte(c)
  78  	fn(uintptr(unsafe.Pointer(&ab[0])), int32(len(ab)),
  79  		uintptr(unsafe.Pointer(&bb[0])), int32(len(bb)),
  80  		uintptr(unsafe.Pointer(&cb[0])), int32(len(cb)))
  81  }
  82  
  83  func bootstrapTypeDesc(t types.Type) string {
  84  	switch t := t.Underlying().(type) {
  85  	case *types.Basic:
  86  		switch t.Kind() {
  87  		case types.Bool:
  88  			return "bool"
  89  		case types.Int8:
  90  			return "int8"
  91  		case types.Int16:
  92  			return "int16"
  93  		case types.Int32, types.Int:
  94  			return "int32"
  95  		case types.Int64:
  96  			return "int64"
  97  		case types.Uint8:
  98  			return "uint8"
  99  		case types.Uint16:
 100  			return "uint16"
 101  		case types.Uint32, types.Uint:
 102  			return "uint32"
 103  		case types.Uint64:
 104  			return "uint64"
 105  		case types.Float32:
 106  			return "float32"
 107  		case types.Float64:
 108  			return "float64"
 109  		case types.String:
 110  			return "string"
 111  		case types.UnsafePointer, types.Uintptr:
 112  			return "ptr"
 113  		}
 114  	case *types.Slice:
 115  		return "[]" + bootstrapTypeDesc(t.Elem())
 116  	case *types.Pointer:
 117  		return "*" + bootstrapTypeDesc(t.Elem())
 118  	}
 119  	return "int32"
 120  }
 121  
 122  func bootstrapSigDesc(sig *types.Signature) string {
 123  	var parts []string
 124  	params := sig.Params()
 125  	for i := 0; i < params.Len(); i++ {
 126  		parts = append(parts, bootstrapTypeDesc(params.At(i).Type()))
 127  	}
 128  	paramStr := strings.Join(parts, ",")
 129  
 130  	results := sig.Results()
 131  	if results.Len() == 0 {
 132  		return paramStr
 133  	}
 134  	var rParts []string
 135  	for i := 0; i < results.Len(); i++ {
 136  		rParts = append(rParts, bootstrapTypeDesc(results.At(i).Type()))
 137  	}
 138  	resultStr := strings.Join(rParts, ",")
 139  	if paramStr == "" {
 140  		return "->" + resultStr
 141  	}
 142  	return paramStr + "->" + resultStr
 143  }
 144  
 145  func bootstrapRegisterImports(pkg *loader.Package) {
 146  	bootstrapClearImport()
 147  	if pkg.Pkg == nil {
 148  		return
 149  	}
 150  	for _, imp := range pkg.Pkg.Imports() {
 151  		bootstrapRegisterStr(bootstrapRegPkg, imp.Path(), imp.Name())
 152  		scope := imp.Scope()
 153  		for _, name := range scope.Names() {
 154  			obj := scope.Lookup(name)
 155  			if !obj.Exported() {
 156  				continue
 157  			}
 158  			switch obj := obj.(type) {
 159  			case *types.Func:
 160  				sig := obj.Type().(*types.Signature)
 161  				bootstrapRegisterStr3(bootstrapRegFunc, imp.Path(), name, bootstrapSigDesc(sig))
 162  			case *types.Var:
 163  				bootstrapRegisterStr3(bootstrapRegVar, imp.Path(), name, bootstrapTypeDesc(obj.Type()))
 164  			}
 165  		}
 166  	}
 167  }
 168  
 169  func bootstrapConcatSources(pkg *loader.Package) ([]byte, error) {
 170  	imports := map[string]bool{}
 171  	var bodies [][]byte
 172  	for _, f := range pkg.GoFiles {
 173  		path := filepath.Join(pkg.Dir, f)
 174  		data, err := os.ReadFile(path)
 175  		if err != nil {
 176  			return nil, fmt.Errorf("bootstrap: read %s: %w", path, err)
 177  		}
 178  		lines := strings.Split(string(data), "\n")
 179  		var body []string
 180  		i := 0
 181  		for i < len(lines) {
 182  			line := strings.TrimSpace(lines[i])
 183  			if strings.HasPrefix(line, "package ") {
 184  				i++
 185  				continue
 186  			}
 187  			if line == "import (" {
 188  				i++
 189  				for i < len(lines) {
 190  					imp := strings.TrimSpace(lines[i])
 191  					i++
 192  					if imp == ")" {
 193  						break
 194  					}
 195  					if imp != "" {
 196  						imports[imp] = true
 197  					}
 198  				}
 199  				continue
 200  			}
 201  			if strings.HasPrefix(line, "import ") && !strings.HasPrefix(line, "import (") {
 202  				imp := strings.TrimPrefix(line, "import ")
 203  				imports[imp] = true
 204  				i++
 205  				continue
 206  			}
 207  			body = append(body, lines[i])
 208  			i++
 209  		}
 210  		bodies = append(bodies, []byte(strings.Join(body, "\n")))
 211  	}
 212  	var out []byte
 213  	out = append(out, []byte("package "+pkg.Pkg.Name()+"\n")...)
 214  	if len(imports) > 0 {
 215  		out = append(out, []byte("import (\n")...)
 216  		for imp := range imports {
 217  			out = append(out, '\t')
 218  			out = append(out, []byte(imp)...)
 219  			out = append(out, '\n')
 220  		}
 221  		out = append(out, []byte(")\n")...)
 222  	}
 223  	for _, b := range bodies {
 224  		out = append(out, b...)
 225  		out = append(out, '\n')
 226  	}
 227  	return out, nil
 228  }
 229  
 230  func bootstrapCompilePackage(pkg *loader.Package, triple string) (llvm.Module, error) {
 231  	initBootstrap()
 232  	if bootstrapErr != nil {
 233  		return llvm.Module{}, bootstrapErr
 234  	}
 235  
 236  	bootstrapRegisterImports(pkg)
 237  
 238  	src, err := bootstrapConcatSources(pkg)
 239  	if err != nil {
 240  		return llvm.Module{}, err
 241  	}
 242  	if len(src) == 0 {
 243  		return llvm.Module{}, fmt.Errorf("bootstrap: no source files for %s", pkg.ImportPath)
 244  	}
 245  
 246  	name := []byte(pkg.Pkg.Path())
 247  	tripleBytes := []byte(triple)
 248  
 249  	h := bootstrapCompile(
 250  		uintptr(unsafe.Pointer(&src[0])), int32(len(src)),
 251  		uintptr(unsafe.Pointer(&name[0])), int32(len(name)),
 252  		uintptr(unsafe.Pointer(&tripleBytes[0])), int32(len(tripleBytes)),
 253  	)
 254  	if h < 0 {
 255  		return llvm.Module{}, fmt.Errorf("bootstrap: compile failed for %s", pkg.ImportPath)
 256  	}
 257  
 258  	n := bootstrapIRLen(h)
 259  	if n <= 0 {
 260  		bootstrapIRFree(h)
 261  		return llvm.Module{}, fmt.Errorf("bootstrap: empty IR for %s", pkg.ImportPath)
 262  	}
 263  	irBuf := make([]byte, n)
 264  	bootstrapIRCopy(h, uintptr(unsafe.Pointer(&irBuf[0])), n)
 265  	bootstrapIRFree(h)
 266  
 267  	if dp := os.Getenv("MOXIE_BOOTSTRAP_DUMP_IR"); dp != "" {
 268  		os.WriteFile(dp, irBuf, 0644)
 269  	}
 270  
 271  	ctx := llvm.NewContext()
 272  	membuf := llvm.NewMemoryBufferFromBytes(irBuf, pkg.ImportPath+".ll")
 273  	mod, err := ctx.ParseIR(membuf)
 274  	if err != nil {
 275  		ctx.Dispose()
 276  		return llvm.Module{}, fmt.Errorf("bootstrap: parse IR for %s: %w", pkg.ImportPath, err)
 277  	}
 278  	return mod, nil
 279  }
 280