all: add support for windows/amd64
This uses Mingw-w64, which seems to be the de facto standard for porting Unixy programs to Windows.
Этот коммит содержится в:
родитель
41bcad9c19
коммит
869e917dc6
26 изменённых файлов: 841 добавлений и 161 удалений
2
.github/workflows/windows.yml
предоставленный
2
.github/workflows/windows.yml
предоставленный
|
@ -99,3 +99,5 @@ jobs:
|
|||
- name: Smoke tests
|
||||
shell: bash
|
||||
run: make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
|
||||
- name: Test stdlib packages
|
||||
run: make tinygo-test
|
||||
|
|
3
.gitmodules
предоставленный
3
.gitmodules
предоставленный
|
@ -29,3 +29,6 @@
|
|||
[submodule "lib/binaryen"]
|
||||
path = lib/binaryen
|
||||
url = https://github.com/WebAssembly/binaryen.git
|
||||
[submodule "lib/mingw-w64"]
|
||||
path = lib/mingw-w64
|
||||
url = https://github.com/mingw-w64/mingw-w64.git
|
||||
|
|
12
Makefile
12
Makefile
|
@ -38,7 +38,7 @@ endif
|
|||
|
||||
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
|
||||
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsmanifest
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE = .exe
|
||||
|
@ -477,7 +477,10 @@ endif
|
|||
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
|
||||
@$(MD5SUM) test.hex
|
||||
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
|
||||
GOOS=windows GOARCH=amd64 $(TINYGO) build -o test.exe ./testdata/cgo
|
||||
ifneq ($(OS),Windows_NT)
|
||||
# TODO: this does not yet work on Windows. Somehow, unused functions are
|
||||
# not garbage collected.
|
||||
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
|
||||
endif
|
||||
|
||||
|
@ -490,6 +493,8 @@ build/release: tinygo gen-device wasi-libc binaryen
|
|||
@mkdir -p build/release/tinygo/lib/clang/include
|
||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
|
||||
@mkdir -p build/release/tinygo/lib/musl/arch
|
||||
@mkdir -p build/release/tinygo/lib/musl/crt
|
||||
@mkdir -p build/release/tinygo/lib/musl/src
|
||||
|
@ -530,6 +535,11 @@ build/release: tinygo gen-device wasi-libc binaryen
|
|||
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||
@cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers
|
||||
@cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
|
||||
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
|
||||
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
|
||||
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
|
||||
|
|
|
@ -3,6 +3,7 @@ package builder
|
|||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -39,29 +40,42 @@ func makeArchive(arfile *os.File, objs []string) error {
|
|||
}
|
||||
|
||||
// Read the symbols and add them to the symbol table.
|
||||
dbg, err := elf.NewFile(objfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file %s: %w", objpath, err)
|
||||
}
|
||||
symbols, err := dbg.Symbols()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, symbol := range symbols {
|
||||
bind := elf.ST_BIND(symbol.Info)
|
||||
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
||||
// Don't include local symbols (STB_LOCAL).
|
||||
continue
|
||||
if dbg, err := elf.NewFile(objfile); err == nil {
|
||||
symbols, err := dbg.Symbols()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
|
||||
// Not a function.
|
||||
continue
|
||||
for _, symbol := range symbols {
|
||||
bind := elf.ST_BIND(symbol.Info)
|
||||
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
||||
// Don't include local symbols (STB_LOCAL).
|
||||
continue
|
||||
}
|
||||
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
|
||||
// Not a function.
|
||||
continue
|
||||
}
|
||||
// Include in archive.
|
||||
symbolTable = append(symbolTable, struct {
|
||||
name string
|
||||
fileIndex int
|
||||
}{symbol.Name, i})
|
||||
}
|
||||
// Include in archive.
|
||||
symbolTable = append(symbolTable, struct {
|
||||
name string
|
||||
fileIndex int
|
||||
}{symbol.Name, i})
|
||||
} else if dbg, err := pe.NewFile(objfile); err == nil {
|
||||
for _, symbol := range dbg.Symbols {
|
||||
if symbol.StorageClass != 2 {
|
||||
continue
|
||||
}
|
||||
if symbol.SectionNumber == 0 {
|
||||
continue
|
||||
}
|
||||
symbolTable = append(symbolTable, struct {
|
||||
name string
|
||||
fileIndex int
|
||||
}{symbol.Name, i})
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("failed to open file %s as ELF or PE/COFF: %w", objpath, err)
|
||||
}
|
||||
|
||||
// Close file, to avoid issues with too many open files (especially on
|
||||
|
@ -120,11 +134,13 @@ func makeArchive(arfile *os.File, objs []string) error {
|
|||
}
|
||||
|
||||
// Add all object files to the archive.
|
||||
var copyBuf bytes.Buffer
|
||||
for i, objpath := range objs {
|
||||
objfile, err := os.Open(objpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer objfile.Close()
|
||||
|
||||
// Store the start index, for when we'll update the symbol table with
|
||||
// the correct file start indices.
|
||||
|
@ -155,13 +171,21 @@ func makeArchive(arfile *os.File, objs []string) error {
|
|||
}
|
||||
|
||||
// Copy the file contents into the archive.
|
||||
n, err := io.Copy(arwriter, objfile)
|
||||
// First load all contents into a buffer, then write it all in one go to
|
||||
// the archive file. This is a bit complicated, but is necessary because
|
||||
// io.Copy can't deal with files that are of an odd size.
|
||||
copyBuf.Reset()
|
||||
n, err := io.Copy(©Buf, objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("could not copy object file into ar file: %w", err)
|
||||
}
|
||||
if n != st.Size() {
|
||||
return errors.New("file modified during ar creation: " + arfile.Name())
|
||||
}
|
||||
_, err = arwriter.Write(copyBuf.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not copy object file into ar file: %w", err)
|
||||
}
|
||||
|
||||
// File is not needed anymore.
|
||||
objfile.Close()
|
||||
|
|
|
@ -115,6 +115,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
|||
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
|
||||
}
|
||||
libcDependencies = append(libcDependencies, dummyCompileJob(path))
|
||||
case "mingw-w64":
|
||||
_, err := MinGW.load(config, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(dir)...)
|
||||
case "":
|
||||
// no library specified, so nothing to do
|
||||
default:
|
||||
|
@ -518,6 +524,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
|||
// Prepare link command.
|
||||
linkerDependencies := []*compileJob{outputObjectFileJob}
|
||||
executable := filepath.Join(dir, "main")
|
||||
if config.GOOS() == "windows" {
|
||||
executable += ".exe"
|
||||
}
|
||||
tmppath := executable // final file
|
||||
ldflags := append(config.LDFlags(), "-o", executable)
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ func TestClangAttributes(t *testing.T) {
|
|||
{GOOS: "linux", GOARCH: "arm64"},
|
||||
{GOOS: "darwin", GOARCH: "amd64"},
|
||||
{GOOS: "darwin", GOARCH: "arm64"},
|
||||
{GOOS: "windows", GOARCH: "amd64"},
|
||||
} {
|
||||
t.Run("GOOS="+options.GOOS+",GOARCH="+options.GOARCH, func(t *testing.T) {
|
||||
testClangAttributes(t, options)
|
||||
|
|
|
@ -11,6 +11,11 @@ bool tinygo_link_elf(int argc, char **argv) {
|
|||
return lld::elf::link(args, false, llvm::outs(), llvm::errs());
|
||||
}
|
||||
|
||||
bool tinygo_link_mingw(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::mingw::link(args, false, llvm::outs(), llvm::errs());
|
||||
}
|
||||
|
||||
bool tinygo_link_wasm(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::wasm::link(args, false, llvm::outs(), llvm::errs());
|
||||
|
|
91
builder/mingw-w64.go
Обычный файл
91
builder/mingw-w64.go
Обычный файл
|
@ -0,0 +1,91 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
var MinGW = Library{
|
||||
name: "mingw-w64",
|
||||
makeHeaders: func(target, includeDir string) error {
|
||||
// copy _mingw.h
|
||||
srcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "mingw-w64")
|
||||
outf, err := os.Create(includeDir + "/_mingw.h")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outf.Close()
|
||||
inf, err := os.Open(srcDir + "/mingw-w64-headers/crt/_mingw.h.in")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(outf, inf)
|
||||
return err
|
||||
},
|
||||
cflags: func(target, headerPath string) []string {
|
||||
// No flags necessary because there are no files to compile.
|
||||
return nil
|
||||
},
|
||||
librarySources: func(target string) []string {
|
||||
// We only use the UCRT DLL file. No source files necessary.
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// makeMinGWExtraLibs returns a slice of jobs to import the correct .dll
|
||||
// libraries. This is done by converting input .def files to .lib files which
|
||||
// can then be linked as usual.
|
||||
//
|
||||
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
|
||||
// compile these files.
|
||||
func makeMinGWExtraLibs(tmpdir string) []*compileJob {
|
||||
var jobs []*compileJob
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
// Normally all the api-ms-win-crt-*.def files are all compiled to a single
|
||||
// .lib file. But to simplify things, we're going to leave them as separate
|
||||
// files.
|
||||
for _, name := range []string{
|
||||
"kernel32.def.in",
|
||||
"api-ms-win-crt-conio-l1-1-0.def",
|
||||
"api-ms-win-crt-convert-l1-1-0.def",
|
||||
"api-ms-win-crt-environment-l1-1-0.def",
|
||||
"api-ms-win-crt-filesystem-l1-1-0.def",
|
||||
"api-ms-win-crt-heap-l1-1-0.def",
|
||||
"api-ms-win-crt-locale-l1-1-0.def",
|
||||
"api-ms-win-crt-math-l1-1-0.def.in",
|
||||
"api-ms-win-crt-multibyte-l1-1-0.def",
|
||||
"api-ms-win-crt-private-l1-1-0.def.in",
|
||||
"api-ms-win-crt-process-l1-1-0.def",
|
||||
"api-ms-win-crt-runtime-l1-1-0.def.in",
|
||||
"api-ms-win-crt-stdio-l1-1-0.def",
|
||||
"api-ms-win-crt-string-l1-1-0.def",
|
||||
"api-ms-win-crt-time-l1-1-0.def",
|
||||
"api-ms-win-crt-utility-l1-1-0.def",
|
||||
} {
|
||||
outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
|
||||
inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
|
||||
job := &compileJob{
|
||||
description: "create lib file " + inpath,
|
||||
result: outpath,
|
||||
run: func(job *compileJob) error {
|
||||
defpath := inpath
|
||||
if strings.HasSuffix(inpath, ".in") {
|
||||
// .in files need to be preprocessed by a preprocessor (-E)
|
||||
// first.
|
||||
defpath = outpath + ".def"
|
||||
err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", "-DDEF_X64", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return link("ld.lld", "-m", "i386pep", "-o", outpath, defpath)
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
}
|
||||
return jobs
|
||||
}
|
|
@ -13,6 +13,7 @@ import (
|
|||
#include <stdlib.h>
|
||||
bool tinygo_clang_driver(int argc, char **argv);
|
||||
bool tinygo_link_elf(int argc, char **argv);
|
||||
bool tinygo_link_mingw(int argc, char **argv);
|
||||
bool tinygo_link_wasm(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
|
@ -24,6 +25,10 @@ const hasBuiltinTools = true
|
|||
// This version actually runs the tools because TinyGo was compiled while
|
||||
// linking statically with LLVM (with the byollvm build tag).
|
||||
func RunTool(tool string, args ...string) error {
|
||||
linker := "elf"
|
||||
if tool == "ld.lld" && len(args) >= 2 && args[0] == "-m" && args[1] == "i386pep" {
|
||||
linker = "mingw"
|
||||
}
|
||||
args = append([]string{"tinygo:" + tool}, args...)
|
||||
|
||||
var cflag *C.char
|
||||
|
@ -41,7 +46,14 @@ func RunTool(tool string, args ...string) error {
|
|||
case "clang":
|
||||
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
|
||||
case "ld.lld":
|
||||
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
|
||||
switch linker {
|
||||
case "elf":
|
||||
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
|
||||
case "mingw":
|
||||
ok = C.tinygo_link_mingw(C.int(len(args)), (**C.char)(buf))
|
||||
default:
|
||||
return errors.New("unknown linker: " + linker)
|
||||
}
|
||||
case "wasm-ld":
|
||||
ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf))
|
||||
default:
|
||||
|
|
|
@ -272,6 +272,15 @@ func (c *Config) CFlags() []string {
|
|||
case "wasi-libc":
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
|
||||
case "mingw-w64":
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
path, _ := c.LibcPath("mingw-w64")
|
||||
cflags = append(cflags,
|
||||
"--sysroot="+path,
|
||||
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
|
||||
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
|
||||
"-D_UCRT",
|
||||
)
|
||||
case "":
|
||||
// No libc specified, nothing to add.
|
||||
default:
|
||||
|
|
|
@ -206,6 +206,9 @@ func LoadTarget(options *Options) (*TargetSpec, error) {
|
|||
if options.GOARCH == "arm" {
|
||||
target += "-gnueabihf"
|
||||
}
|
||||
if options.GOOS == "windows" {
|
||||
target += "-gnu"
|
||||
}
|
||||
return defaultTarget(options.GOOS, options.GOARCH, target)
|
||||
}
|
||||
|
||||
|
@ -230,13 +233,7 @@ func LoadTarget(options *Options) (*TargetSpec, error) {
|
|||
return spec, nil
|
||||
}
|
||||
|
||||
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
|
||||
var WindowsBuildNotSupportedErr = errors.New("Building Windows binaries is currently not supported. Try specifying a different target")
|
||||
|
||||
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
if goos == "windows" {
|
||||
return nil, WindowsBuildNotSupportedErr
|
||||
}
|
||||
// No target spec available. Use the default one, useful on most systems
|
||||
// with a regular OS.
|
||||
spec := TargetSpec{
|
||||
|
@ -279,12 +276,28 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|||
spec.RTLib = "compiler-rt"
|
||||
spec.Libc = "musl"
|
||||
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
|
||||
} else if goos == "windows" {
|
||||
spec.Linker = "ld.lld"
|
||||
spec.Libc = "mingw-w64"
|
||||
spec.LDFlags = append(spec.LDFlags,
|
||||
"-m", "i386pep",
|
||||
"-Bdynamic",
|
||||
"--image-base", "0x400000",
|
||||
"--gc-sections",
|
||||
"--no-insert-timestamp",
|
||||
)
|
||||
} else {
|
||||
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
|
||||
}
|
||||
if goarch != "wasm" {
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
|
||||
suffix := ""
|
||||
if goos == "windows" {
|
||||
// Windows uses a different calling convention from other operating
|
||||
// systems so we need separate assembly files.
|
||||
suffix = "_windows"
|
||||
}
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+suffix+".S")
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S")
|
||||
}
|
||||
if goarch != runtime.GOARCH {
|
||||
// Some educated guesses as to how to invoke helper programs.
|
||||
|
@ -296,6 +309,11 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|||
spec.Emulator = []string{"qemu-aarch64"}
|
||||
}
|
||||
}
|
||||
if goos != runtime.GOOS {
|
||||
if goos == "windows" {
|
||||
spec.Emulator = []string{"wine"}
|
||||
}
|
||||
}
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -156,12 +156,12 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||
// createSyscall emits instructions for the syscall.Syscall* family of
|
||||
// functions, depending on the target OS/arch.
|
||||
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
syscallResult, err := b.createRawSyscall(call)
|
||||
if err != nil {
|
||||
return syscallResult, err
|
||||
}
|
||||
switch b.GOOS {
|
||||
case "linux", "freebsd":
|
||||
syscallResult, err := b.createRawSyscall(call)
|
||||
if err != nil {
|
||||
return syscallResult, err
|
||||
}
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
|
@ -180,6 +180,10 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
case "darwin":
|
||||
syscallResult, err := b.createRawSyscall(call)
|
||||
if err != nil {
|
||||
return syscallResult, err
|
||||
}
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
|
@ -195,6 +199,57 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||
retval = b.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
case "windows":
|
||||
// On Windows, syscall.Syscall* is basically just a function pointer
|
||||
// call. This is complicated in gc because of stack switching and the
|
||||
// different ABI, but easy in TinyGo: just call the function pointer.
|
||||
// The signature looks like this:
|
||||
// func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
|
||||
|
||||
// Prepare input values.
|
||||
var paramTypes []llvm.Type
|
||||
var params []llvm.Value
|
||||
for _, val := range call.Args[2:] {
|
||||
param := b.getValue(val)
|
||||
params = append(params, param)
|
||||
paramTypes = append(paramTypes, param.Type())
|
||||
}
|
||||
llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
|
||||
fn := b.getValue(call.Args[0])
|
||||
fnPtr := b.CreateIntToPtr(fn, llvm.PointerType(llvmType, 0), "")
|
||||
|
||||
// Prepare some functions that will be called later.
|
||||
setLastError := b.mod.NamedFunction("SetLastError")
|
||||
if setLastError.IsNil() {
|
||||
llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
|
||||
setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
|
||||
}
|
||||
getLastError := b.mod.NamedFunction("GetLastError")
|
||||
if getLastError.IsNil() {
|
||||
llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
|
||||
getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
|
||||
}
|
||||
|
||||
// Now do the actual call. Pseudocode:
|
||||
// SetLastError(0)
|
||||
// r1 = trap(a1, a2, a3, ...)
|
||||
// err = uintptr(GetLastError())
|
||||
// return r1, 0, err
|
||||
// Note that SetLastError/GetLastError could be replaced with direct
|
||||
// access to the thread control block, which is probably smaller and
|
||||
// faster. The Go runtime does this in assembly.
|
||||
b.CreateCall(setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
|
||||
syscallResult := b.CreateCall(fnPtr, params, "")
|
||||
errResult := b.CreateCall(getLastError, nil, "err")
|
||||
if b.uintptrType != b.ctx.Int32Type() {
|
||||
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
|
||||
}
|
||||
|
||||
// Return r1, 0, err
|
||||
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
|
||||
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
default:
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
|
||||
}
|
||||
|
|
1
lib/mingw-w64
Подмодуль
1
lib/mingw-w64
Подмодуль
|
@ -0,0 +1 @@
|
|||
Subproject commit acc9b9d9eb63a13d8122cbac4882eb5f4ee2f679
|
25
main_test.go
25
main_test.go
|
@ -71,11 +71,9 @@ func TestCompiler(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Run("Host", func(t *testing.T) {
|
||||
runPlatTests(optionsFromTarget(""), tests, t)
|
||||
})
|
||||
}
|
||||
t.Run("Host", func(t *testing.T) {
|
||||
runPlatTests(optionsFromTarget(""), tests, t)
|
||||
})
|
||||
|
||||
if testing.Short() {
|
||||
return
|
||||
|
@ -113,10 +111,6 @@ func TestCompiler(t *testing.T) {
|
|||
|
||||
// Test a few build options.
|
||||
t.Run("build-options", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// These tests assume a host that is supported by TinyGo.
|
||||
t.Skip("can't test build options on Windows")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
// Test with few optimizations enabled (no inlining, etc).
|
||||
|
@ -298,6 +292,9 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
|||
|
||||
// Build the test binary.
|
||||
binary := filepath.Join(tmpdir, "test")
|
||||
if spec.GOOS == "windows" {
|
||||
binary += ".exe"
|
||||
}
|
||||
err = runBuild("./"+path, binary, &options)
|
||||
if err != nil {
|
||||
printCompilerError(t.Log, err)
|
||||
|
@ -339,7 +336,13 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
|||
}
|
||||
go func() {
|
||||
// Terminate the process if it runs too long.
|
||||
timer := time.NewTimer(10 * time.Second)
|
||||
maxDuration := 10 * time.Second
|
||||
if runtime.GOOS == "windows" {
|
||||
// For some reason, tests on Windows can take around
|
||||
// 30s to complete. TODO: investigate why and fix this.
|
||||
maxDuration = 40 * time.Second
|
||||
}
|
||||
timer := time.NewTimer(maxDuration)
|
||||
select {
|
||||
case <-runComplete:
|
||||
timer.Stop()
|
||||
|
@ -369,7 +372,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
|||
t.Log("failed to run:", err)
|
||||
fail = true
|
||||
} else if !bytes.Equal(expected, actual) {
|
||||
t.Log("output did not match")
|
||||
t.Logf("output did not match (expected %d bytes, got %d bytes):", len(expected), len(actual))
|
||||
fail = true
|
||||
}
|
||||
|
||||
|
|
42
src/crypto/rand/rand_windows.go
Обычный файл
42
src/crypto/rand/rand_windows.go
Обычный файл
|
@ -0,0 +1,42 @@
|
|||
package rand
|
||||
|
||||
import "errors"
|
||||
|
||||
func init() {
|
||||
Reader = &reader{}
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
}
|
||||
|
||||
var errRandom = errors.New("failed to obtain random data from rand_s")
|
||||
|
||||
func (r *reader) Read(b []byte) (n int, err error) {
|
||||
if len(b) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var randomByte uint32
|
||||
for i := range b {
|
||||
// Call rand_s every four bytes because it's a C int (always 32-bit in
|
||||
// Windows).
|
||||
if i%4 == 0 {
|
||||
errCode := libc_rand_s(&randomByte)
|
||||
if errCode != 0 {
|
||||
// According to the documentation, it can return an error.
|
||||
return n, errRandom
|
||||
}
|
||||
} else {
|
||||
randomByte >>= 8
|
||||
}
|
||||
b[i] = byte(randomByte)
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Cryptographically secure random number generator.
|
||||
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rand-s?view=msvc-170
|
||||
// errno_t rand_s(unsigned int* randomValue);
|
||||
//export rand_s
|
||||
func libc_rand_s(randomValue *uint32) int32
|
|
@ -1,4 +1,4 @@
|
|||
// +build scheduler.tasks,amd64
|
||||
// +build scheduler.tasks,amd64,!windows
|
||||
|
||||
package task
|
||||
|
||||
|
|
57
src/internal/task/task_stack_amd64_windows.S
Обычный файл
57
src/internal/task/task_stack_amd64_windows.S
Обычный файл
|
@ -0,0 +1,57 @@
|
|||
// Windows on amd64 has a slightly different ABI than other (*nix) systems.
|
||||
// Therefore, assembly functions need to be tweaked slightly.
|
||||
|
||||
.section .text.tinygo_startTask,"ax"
|
||||
.global tinygo_startTask
|
||||
tinygo_startTask:
|
||||
// Small assembly stub for starting a goroutine. This is already run on the
|
||||
// new stack, with the callee-saved registers already loaded.
|
||||
// Most importantly, r12 contain the pc of the to-be-started function and
|
||||
// r13 contain the only argument it is given. Multiple arguments are packed
|
||||
// into one by storing them in a new allocation.
|
||||
|
||||
// Set the first argument of the goroutine start wrapper, which contains all
|
||||
// the arguments.
|
||||
movq %r13, %rcx
|
||||
|
||||
// Branch to the "goroutine start" function.
|
||||
callq *%r12
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
jmp tinygo_pause
|
||||
|
||||
.global tinygo_swapTask
|
||||
.section .text.tinygo_swapTask,"ax"
|
||||
tinygo_swapTask:
|
||||
// This function gets the following parameters:
|
||||
// %rcx = newStack uintptr
|
||||
// %rdx = oldStack *uintptr
|
||||
|
||||
// Save all callee-saved registers:
|
||||
pushq %r15
|
||||
pushq %r14
|
||||
pushq %r13
|
||||
pushq %r12
|
||||
pushq %rsi
|
||||
pushq %rdi
|
||||
pushq %rbp
|
||||
pushq %rbx
|
||||
|
||||
// Save the current stack pointer in oldStack.
|
||||
movq %rsp, (%rdx)
|
||||
|
||||
// Switch to the new stack pointer.
|
||||
movq %rcx, %rsp
|
||||
|
||||
// Load saved register from the new stack.
|
||||
popq %rbx
|
||||
popq %rbp
|
||||
popq %rdi
|
||||
popq %rsi
|
||||
popq %r12
|
||||
popq %r13
|
||||
popq %r14
|
||||
popq %r15
|
||||
|
||||
// Return into the new task, as if tinygo_swapTask was a regular call.
|
||||
ret
|
66
src/internal/task/task_stack_amd64_windows.go
Обычный файл
66
src/internal/task/task_stack_amd64_windows.go
Обычный файл
|
@ -0,0 +1,66 @@
|
|||
// +build scheduler.tasks,amd64,windows
|
||||
|
||||
package task
|
||||
|
||||
// This is almost the same as task_stack_amd64.go, but with the extra rdi and
|
||||
// rsi registers saved: Windows has a slightly different calling convention.
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_amd64_windows.S that relies on
|
||||
// the exact layout of this struct.
|
||||
type calleeSavedRegs struct {
|
||||
rbx uintptr
|
||||
rbp uintptr
|
||||
rdi uintptr
|
||||
rsi uintptr
|
||||
r12 uintptr
|
||||
r13 uintptr
|
||||
r14 uintptr
|
||||
r15 uintptr
|
||||
|
||||
pc uintptr
|
||||
}
|
||||
|
||||
// archInit runs architecture-specific setup for the goroutine startup.
|
||||
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
// Store the initial sp for the startTask function (implemented in assembly).
|
||||
s.sp = uintptr(unsafe.Pointer(r))
|
||||
|
||||
// Initialize the registers.
|
||||
// These will be popped off of the stack on the first resume of the goroutine.
|
||||
|
||||
// Start the function at tinygo_startTask (defined in
|
||||
// src/internal/task/task_stack_amd64_windows.S). This assembly code calls a
|
||||
// function (passed in r12) with a single argument (passed in r13). After
|
||||
// the function returns, it calls Pause().
|
||||
r.pc = uintptr(unsafe.Pointer(&startTask))
|
||||
|
||||
// Pass the function to call in r12.
|
||||
// This function is a compiler-generated wrapper which loads arguments out
|
||||
// of a struct pointer. See createGoroutineStartWrapper (defined in
|
||||
// compiler/goroutine.go) for more information.
|
||||
r.r12 = fn
|
||||
|
||||
// Pass the pointer to the arguments struct in r13.
|
||||
r.r13 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
}
|
114
src/os/file_anyos.go
Обычный файл
114
src/os/file_anyos.go
Обычный файл
|
@ -0,0 +1,114 @@
|
|||
// +build !baremetal,!js
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Mount the host filesystem at the root directory. This is what most
|
||||
// programs will be expecting.
|
||||
Mount("/", unixFilesystem{})
|
||||
}
|
||||
|
||||
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
|
||||
// standard output, and standard error file descriptors.
|
||||
var (
|
||||
Stdin = &File{unixFileHandle(syscall.Stdin), "/dev/stdin"}
|
||||
Stdout = &File{unixFileHandle(syscall.Stdout), "/dev/stdout"}
|
||||
Stderr = &File{unixFileHandle(syscall.Stderr), "/dev/stderr"}
|
||||
)
|
||||
|
||||
// isOS indicates whether we're running on a real operating system with
|
||||
// filesystem support.
|
||||
const isOS = true
|
||||
|
||||
// unixFilesystem is an empty handle for a Unix/Linux filesystem. All operations
|
||||
// are relative to the current working directory.
|
||||
type unixFilesystem struct {
|
||||
}
|
||||
|
||||
func (fs unixFilesystem) Mkdir(path string, perm FileMode) error {
|
||||
return handleSyscallError(syscall.Mkdir(path, uint32(perm)))
|
||||
}
|
||||
|
||||
func (fs unixFilesystem) Remove(path string) error {
|
||||
return handleSyscallError(syscall.Unlink(path))
|
||||
}
|
||||
|
||||
func (fs unixFilesystem) OpenFile(path string, flag int, perm FileMode) (FileHandle, error) {
|
||||
// Map os package flags to syscall flags.
|
||||
syscallFlag := 0
|
||||
if flag&O_RDONLY != 0 {
|
||||
syscallFlag |= syscall.O_RDONLY
|
||||
}
|
||||
if flag&O_WRONLY != 0 {
|
||||
syscallFlag |= syscall.O_WRONLY
|
||||
}
|
||||
if flag&O_RDWR != 0 {
|
||||
syscallFlag |= syscall.O_RDWR
|
||||
}
|
||||
if flag&O_APPEND != 0 {
|
||||
syscallFlag |= syscall.O_APPEND
|
||||
}
|
||||
if flag&O_CREATE != 0 {
|
||||
syscallFlag |= syscall.O_CREAT
|
||||
}
|
||||
if flag&O_EXCL != 0 {
|
||||
syscallFlag |= syscall.O_EXCL
|
||||
}
|
||||
if flag&O_SYNC != 0 {
|
||||
syscallFlag |= syscall.O_SYNC
|
||||
}
|
||||
if flag&O_TRUNC != 0 {
|
||||
syscallFlag |= syscall.O_TRUNC
|
||||
}
|
||||
fp, err := syscall.Open(path, syscallFlag, uint32(perm))
|
||||
return unixFileHandle(fp), handleSyscallError(err)
|
||||
}
|
||||
|
||||
// unixFileHandle is a Unix file pointer with associated methods that implement
|
||||
// the FileHandle interface.
|
||||
type unixFileHandle uintptr
|
||||
|
||||
// Read reads up to len(b) bytes from the File. It returns the number of bytes
|
||||
// read and any error encountered. At end of file, Read returns 0, io.EOF.
|
||||
func (f unixFileHandle) Read(b []byte) (n int, err error) {
|
||||
n, err = syscall.Read(syscallFd(f), b)
|
||||
err = handleSyscallError(err)
|
||||
if n == 0 && err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write writes len(b) bytes to the File. It returns the number of bytes written
|
||||
// and an error, if any. Write returns a non-nil error when n != len(b).
|
||||
func (f unixFileHandle) Write(b []byte) (n int, err error) {
|
||||
n, err = syscall.Write(syscallFd(f), b)
|
||||
err = handleSyscallError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the File, rendering it unusable for I/O.
|
||||
func (f unixFileHandle) Close() error {
|
||||
return handleSyscallError(syscall.Close(syscallFd(f)))
|
||||
}
|
||||
|
||||
// handleSyscallError converts syscall errors into regular os package errors.
|
||||
// The err parameter must be either nil or of type syscall.Errno.
|
||||
func handleSyscallError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
switch err.(syscall.Errno) {
|
||||
case syscall.EEXIST:
|
||||
return ErrExist
|
||||
case syscall.ENOENT:
|
||||
return ErrNotExist
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
|
@ -2,113 +2,4 @@
|
|||
|
||||
package os
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Mount the host filesystem at the root directory. This is what most
|
||||
// programs will be expecting.
|
||||
Mount("/", unixFilesystem{})
|
||||
}
|
||||
|
||||
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
|
||||
// standard output, and standard error file descriptors.
|
||||
var (
|
||||
Stdin = &File{unixFileHandle(0), "/dev/stdin"}
|
||||
Stdout = &File{unixFileHandle(1), "/dev/stdout"}
|
||||
Stderr = &File{unixFileHandle(2), "/dev/stderr"}
|
||||
)
|
||||
|
||||
// isOS indicates whether we're running on a real operating system with
|
||||
// filesystem support.
|
||||
const isOS = true
|
||||
|
||||
// unixFilesystem is an empty handle for a Unix/Linux filesystem. All operations
|
||||
// are relative to the current working directory.
|
||||
type unixFilesystem struct {
|
||||
}
|
||||
|
||||
func (fs unixFilesystem) Mkdir(path string, perm FileMode) error {
|
||||
return handleSyscallError(syscall.Mkdir(path, uint32(perm)))
|
||||
}
|
||||
|
||||
func (fs unixFilesystem) Remove(path string) error {
|
||||
return handleSyscallError(syscall.Unlink(path))
|
||||
}
|
||||
|
||||
func (fs unixFilesystem) OpenFile(path string, flag int, perm FileMode) (FileHandle, error) {
|
||||
// Map os package flags to syscall flags.
|
||||
syscallFlag := 0
|
||||
if flag&O_RDONLY != 0 {
|
||||
syscallFlag |= syscall.O_RDONLY
|
||||
}
|
||||
if flag&O_WRONLY != 0 {
|
||||
syscallFlag |= syscall.O_WRONLY
|
||||
}
|
||||
if flag&O_RDWR != 0 {
|
||||
syscallFlag |= syscall.O_RDWR
|
||||
}
|
||||
if flag&O_APPEND != 0 {
|
||||
syscallFlag |= syscall.O_APPEND
|
||||
}
|
||||
if flag&O_CREATE != 0 {
|
||||
syscallFlag |= syscall.O_CREAT
|
||||
}
|
||||
if flag&O_EXCL != 0 {
|
||||
syscallFlag |= syscall.O_EXCL
|
||||
}
|
||||
if flag&O_SYNC != 0 {
|
||||
syscallFlag |= syscall.O_SYNC
|
||||
}
|
||||
if flag&O_TRUNC != 0 {
|
||||
syscallFlag |= syscall.O_TRUNC
|
||||
}
|
||||
fp, err := syscall.Open(path, syscallFlag, uint32(perm))
|
||||
return unixFileHandle(fp), handleSyscallError(err)
|
||||
}
|
||||
|
||||
// unixFileHandle is a Unix file pointer with associated methods that implement
|
||||
// the FileHandle interface.
|
||||
type unixFileHandle uintptr
|
||||
|
||||
// Read reads up to len(b) bytes from the File. It returns the number of bytes
|
||||
// read and any error encountered. At end of file, Read returns 0, io.EOF.
|
||||
func (f unixFileHandle) Read(b []byte) (n int, err error) {
|
||||
n, err = syscall.Read(int(f), b)
|
||||
err = handleSyscallError(err)
|
||||
if n == 0 && err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write writes len(b) bytes to the File. It returns the number of bytes written
|
||||
// and an error, if any. Write returns a non-nil error when n != len(b).
|
||||
func (f unixFileHandle) Write(b []byte) (n int, err error) {
|
||||
n, err = syscall.Write(int(f), b)
|
||||
err = handleSyscallError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the File, rendering it unusable for I/O.
|
||||
func (f unixFileHandle) Close() error {
|
||||
return handleSyscallError(syscall.Close(int(f)))
|
||||
}
|
||||
|
||||
// handleSyscallError converts syscall errors into regular os package errors.
|
||||
// The err parameter must be either nil or of type syscall.Errno.
|
||||
func handleSyscallError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
switch err.(syscall.Errno) {
|
||||
case syscall.EEXIST:
|
||||
return ErrExist
|
||||
case syscall.ENOENT:
|
||||
return ErrNotExist
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
type syscallFd = int
|
||||
|
|
7
src/os/file_windows.go
Обычный файл
7
src/os/file_windows.go
Обычный файл
|
@ -0,0 +1,7 @@
|
|||
// +build windows
|
||||
|
||||
package os
|
||||
|
||||
import "syscall"
|
||||
|
||||
type syscallFd = syscall.Handle
|
22
src/runtime/gc_amd64_windows.S
Обычный файл
22
src/runtime/gc_amd64_windows.S
Обычный файл
|
@ -0,0 +1,22 @@
|
|||
.section .text.tinygo_scanCurrentStack,"ax"
|
||||
.global tinygo_scanCurrentStack
|
||||
tinygo_scanCurrentStack:
|
||||
// Save callee-saved registers.
|
||||
pushq %rbx
|
||||
pushq %rbp
|
||||
pushq %rdi
|
||||
pushq %rsi
|
||||
pushq %r12
|
||||
pushq %r13
|
||||
pushq %r14
|
||||
pushq %r15
|
||||
|
||||
// Scan the stack.
|
||||
subq $8, %rsp // adjust the stack before the call to maintain 16-byte alignment
|
||||
movq %rsp, %rdi
|
||||
callq tinygo_scanstack
|
||||
|
||||
// Restore the stack pointer. Registers do not need to be restored as they
|
||||
// were only pushed to be discoverable by the GC.
|
||||
addq $72, %rsp
|
||||
retq
|
3
src/runtime/os_windows.go
Обычный файл
3
src/runtime/os_windows.go
Обычный файл
|
@ -0,0 +1,3 @@
|
|||
package runtime
|
||||
|
||||
const GOOS = "windows"
|
230
src/runtime/runtime_windows.go
Обычный файл
230
src/runtime/runtime_windows.go
Обычный файл
|
@ -0,0 +1,230 @@
|
|||
package runtime
|
||||
|
||||
import "unsafe"
|
||||
|
||||
//export abort
|
||||
func abort()
|
||||
|
||||
//export exit
|
||||
func libc_exit(code int)
|
||||
|
||||
//export putchar
|
||||
func libc_putchar(c int) int
|
||||
|
||||
//export VirtualAlloc
|
||||
func _VirtualAlloc(lpAddress unsafe.Pointer, dwSize uintptr, flAllocationType, flProtect uint32) unsafe.Pointer
|
||||
|
||||
//export QueryUnbiasedInterruptTime
|
||||
func _QueryUnbiasedInterruptTime(UnbiasedTime *uint64) bool
|
||||
|
||||
// The parameter is really a LPFILETIME, but *uint64 should be compatible.
|
||||
//export GetSystemTimeAsFileTime
|
||||
func _GetSystemTimeAsFileTime(lpSystemTimeAsFileTime *uint64)
|
||||
|
||||
//export LoadLibraryExW
|
||||
func _LoadLibraryExW(lpLibFileName *uint16, hFile uintptr, dwFlags uint32) uintptr
|
||||
|
||||
//export Sleep
|
||||
func _Sleep(milliseconds uint32)
|
||||
|
||||
const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
|
||||
|
||||
//export GetProcAddress
|
||||
func getProcAddress(handle uintptr, procname *byte) uintptr
|
||||
|
||||
//export _configure_narrow_argv
|
||||
func _configure_narrow_argv(int32) int32
|
||||
|
||||
//export __p___argc
|
||||
func __p___argc() *int32
|
||||
|
||||
//export __p___argv
|
||||
func __p___argv() **unsafe.Pointer
|
||||
|
||||
func postinit() {}
|
||||
|
||||
//export mainCRTStartup
|
||||
func mainCRTStartup() int {
|
||||
preinit()
|
||||
|
||||
// Obtain the initial stack pointer right before calling the run() function.
|
||||
// The run function has been moved to a separate (non-inlined) function so
|
||||
// that the correct stack pointer is read.
|
||||
stackTop = getCurrentStackPointer()
|
||||
runMain()
|
||||
|
||||
// For libc compatibility.
|
||||
return 0
|
||||
}
|
||||
|
||||
// Must be a separate function to get the correct stack pointer.
|
||||
//go:noinline
|
||||
func runMain() {
|
||||
run()
|
||||
}
|
||||
|
||||
var args []string
|
||||
|
||||
//go:linkname os_runtime_args os.runtime_args
|
||||
func os_runtime_args() []string {
|
||||
if args == nil {
|
||||
// Obtain argc/argv from the environment.
|
||||
_configure_narrow_argv(2)
|
||||
argc := *__p___argc()
|
||||
argv := *__p___argv()
|
||||
|
||||
// Make args slice big enough so that it can store all command line
|
||||
// arguments.
|
||||
args = make([]string, argc)
|
||||
|
||||
// Initialize command line parameters.
|
||||
for i := 0; i < int(argc); i++ {
|
||||
// Convert the C string to a Go string.
|
||||
length := strlen(*argv)
|
||||
arg := (*_string)(unsafe.Pointer(&args[i]))
|
||||
arg.length = length
|
||||
arg.ptr = (*byte)(*argv)
|
||||
// This is the Go equivalent of "argv++" in C.
|
||||
argv = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + unsafe.Sizeof(argv)))
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func putchar(c byte) {
|
||||
libc_putchar(int(c))
|
||||
}
|
||||
|
||||
var heapSize uintptr = 128 * 1024 // small amount to start
|
||||
var heapMaxSize uintptr
|
||||
|
||||
var heapStart, heapEnd uintptr
|
||||
|
||||
func preinit() {
|
||||
// Allocate a large chunk of virtual memory. Because it is virtual, it won't
|
||||
// really be allocated in RAM. Memory will only be allocated when it is
|
||||
// first touched.
|
||||
heapMaxSize = 1 * 1024 * 1024 * 1024 // 1GB for the entire heap
|
||||
const (
|
||||
MEM_COMMIT = 0x00001000
|
||||
MEM_RESERVE = 0x00002000
|
||||
PAGE_READWRITE = 0x04
|
||||
)
|
||||
heapStart = uintptr(_VirtualAlloc(nil, heapMaxSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE))
|
||||
heapEnd = heapStart + heapSize
|
||||
}
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
var stackTop uintptr
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
// Interrupt time count works in units of 100 nanoseconds.
|
||||
return int64(ticks) * 100
|
||||
}
|
||||
|
||||
func nanosecondsToTicks(ns int64) timeUnit {
|
||||
// Interrupt time count works in units of 100 nanoseconds.
|
||||
return timeUnit(ns) / 100
|
||||
}
|
||||
|
||||
func sleepTicks(d timeUnit) {
|
||||
// Calcluate milliseconds from ticks (which have a resolution of 100ns),
|
||||
// rounding up.
|
||||
milliseconds := int64(d+9_999) / 10_000
|
||||
for milliseconds != 0 {
|
||||
duration := uint32(milliseconds)
|
||||
_Sleep(duration)
|
||||
milliseconds -= int64(duration)
|
||||
}
|
||||
}
|
||||
|
||||
func ticks() timeUnit {
|
||||
var unbiasedTime uint64
|
||||
_QueryUnbiasedInterruptTime(&unbiasedTime)
|
||||
return timeUnit(unbiasedTime)
|
||||
}
|
||||
|
||||
//go:linkname now time.now
|
||||
func now() (sec int64, nsec int32, mono int64) {
|
||||
// Get the current time in Windows "file time" format.
|
||||
var time uint64
|
||||
_GetSystemTimeAsFileTime(&time)
|
||||
|
||||
// Convert file time to Unix time.
|
||||
// According to the documentation:
|
||||
// > Contains a 64-bit value representing the number of 100-nanosecond
|
||||
// > intervals since January 1, 1601 (UTC).
|
||||
// We'll convert it to 100 nanosecond intervals starting at 1970.
|
||||
const (
|
||||
// number of 100-nanosecond intervals in a second
|
||||
intervalsPerSecond = 10_000_000
|
||||
secondsPerDay = 60 * 60 * 24
|
||||
// Number of days between the Windows epoch (1 january 1601) and the
|
||||
// Unix epoch (1 january 1970). Source:
|
||||
// https://www.wolframalpha.com/input/?i=days+between+1+january+1601+and+1+january+1970
|
||||
days = 134774
|
||||
)
|
||||
time -= days * secondsPerDay * intervalsPerSecond
|
||||
|
||||
// Convert the time (in 100ns units) to sec/nsec/mono as expected by the
|
||||
// time package.
|
||||
sec = int64(time / intervalsPerSecond)
|
||||
nsec = int32((time - (uint64(sec) * intervalsPerSecond)) * 100)
|
||||
mono = ticksToNanoseconds(ticks())
|
||||
return
|
||||
}
|
||||
|
||||
//go:linkname syscall_Exit syscall.Exit
|
||||
func syscall_Exit(code int) {
|
||||
libc_exit(code)
|
||||
}
|
||||
|
||||
func growHeap() bool {
|
||||
if heapSize == heapMaxSize {
|
||||
// Already at the max. If we run out of memory, we should consider
|
||||
// increasing heapMaxSize..
|
||||
return false
|
||||
}
|
||||
// Grow the heap size used by the program.
|
||||
heapSize = (heapSize * 4 / 3) &^ 4095 // grow by around 33%
|
||||
if heapSize > heapMaxSize {
|
||||
heapSize = heapMaxSize
|
||||
}
|
||||
setHeapEnd(heapStart + heapSize)
|
||||
return true
|
||||
}
|
||||
|
||||
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
|
||||
func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
|
||||
handle = _LoadLibraryExW(filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
if handle == 0 {
|
||||
panic("todo: get error")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//go:linkname syscall_loadlibrary syscall.loadlibrary
|
||||
func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
|
||||
panic("todo: syscall.loadlibrary")
|
||||
}
|
||||
|
||||
//go:linkname syscall_getprocaddress syscall.getprocaddress
|
||||
func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) {
|
||||
outhandle = getProcAddress(handle, procname)
|
||||
if outhandle == 0 {
|
||||
panic("todo: get error")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TinyGo does not yet support any form of parallelism on Windows, so these can
|
||||
// be left empty.
|
||||
|
||||
//go:linkname procPin sync/atomic.runtime_procPin
|
||||
func procPin() {
|
||||
}
|
||||
|
||||
//go:linkname procUnpin sync/atomic.runtime_procUnpin
|
||||
func procUnpin() {
|
||||
}
|
|
@ -62,6 +62,12 @@ const (
|
|||
SIGTERM Signal = 0xf
|
||||
)
|
||||
|
||||
const (
|
||||
Stdin = 0
|
||||
Stdout = 1
|
||||
Stderr = 2
|
||||
)
|
||||
|
||||
const (
|
||||
O_RDONLY = 0x0
|
||||
O_WRONLY = 0x1
|
||||
|
|
3
testdata/filesystem.go
предоставленный
3
testdata/filesystem.go
предоставленный
|
@ -33,6 +33,5 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
print(string(data))
|
||||
|
||||
os.Stdout.Write(data)
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче