diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 46ec2cc7c9..20c5cdb5ed 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -92,7 +92,7 @@ jobs: - name: make gen-device run: make -j3 gen-device - name: Test TinyGo - run: make test GOTESTFLAGS="-short" + run: make test GOTESTFLAGS="-only-current-os" - name: Build TinyGo release tarball run: make release -j3 - name: Test stdlib packages diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 692790bafe..a32117be45 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -105,7 +105,7 @@ jobs: run: make -j3 gen-device - name: Test TinyGo shell: bash - run: make test GOTESTFLAGS="-short" + run: make test GOTESTFLAGS="-only-current-os" - name: Build TinyGo release tarball shell: bash run: make build/release -j4 diff --git a/GNUmakefile b/GNUmakefile index 58568d3eca..a685c08487 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -940,8 +940,9 @@ build/release: tinygo gen-device $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binary @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/macos-minimal-sdk + @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt + @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common - @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio @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 @@ -997,10 +998,17 @@ endif @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/musl/src/process build/release/tinygo/lib/musl/src + @cp -rp lib/mingw-w64/mingw-w64-crt/crt/pseudo-reloc.c build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt @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/gdtoa build/release/tinygo/lib/mingw-w64/mingw-w64-crt + @cp -rp lib/mingw-w64/mingw-w64-crt/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/advapi32.def.in 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-crt/stdio/ucrt_* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio + @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/msvcrt.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + @cp -rp lib/mingw-w64/mingw-w64-crt/math/x86 build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math + @cp -rp lib/mingw-w64/mingw-w64-crt/misc build/release/tinygo/lib/mingw-w64/mingw-w64-crt + @cp -rp lib/mingw-w64/mingw-w64-crt/stdio build/release/tinygo/lib/mingw-w64/mingw-w64-crt @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/mingw-w64/mingw-w64-headers/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers diff --git a/builder/builder_test.go b/builder/builder_test.go index ccccef30ba..6b84b10070 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -67,6 +67,7 @@ func TestClangAttributes(t *testing.T) { {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, + {GOOS: "windows", GOARCH: "386"}, {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "arm64"}, } { diff --git a/builder/builtins.go b/builder/builtins.go index b493b6680a..5e91c78915 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -3,6 +3,7 @@ package builder import ( "os" "path/filepath" + "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" @@ -201,6 +202,11 @@ var avrBuiltins = []string{ "avr/udivmodqi4.S", } +// Builtins needed specifically for windows/386. +var windowsI386Builtins = []string{ + "i386/chkstk.S", // also _alloca +} + // libCompilerRT is a library with symbols required by programs compiled with // LLVM. These symbols are for operations that cannot be emitted with a single // instruction or a short sequence of instructions for that target. @@ -229,6 +235,10 @@ var libCompilerRT = Library{ builtins = append(builtins, avrBuiltins...) case "x86_64", "aarch64", "riscv64": // any 64-bit arch builtins = append(builtins, genericBuiltins128...) + case "i386": + if strings.Split(target, "-")[2] == "windows" { + builtins = append(builtins, windowsI386Builtins...) + } } return builtins, nil }, diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go index 32cf58f531..6bbced8211 100644 --- a/builder/mingw-w64.go +++ b/builder/mingw-w64.go @@ -30,25 +30,62 @@ var libMinGW = Library{ sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") }, cflags: func(target, headerPath string) []string { mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") - return []string{ + flags := []string{ "-nostdlibinc", + "-isystem", mingwDir + "/mingw-w64-crt/include", "-isystem", mingwDir + "/mingw-w64-headers/crt", + "-isystem", mingwDir + "/mingw-w64-headers/include", "-I", mingwDir + "/mingw-w64-headers/defaults/include", "-I" + headerPath, } + if strings.Split(target, "-")[0] == "i386" { + flags = append(flags, + "-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002 + "-D_WIN32_WINNT=0x0501", // target Windows XP + "-D_CRTBLD", + "-Wno-pragma-pack", + ) + } + return flags }, librarySources: func(target string) ([]string, error) { // These files are needed so that printf and the like are supported. - sources := []string{ - "mingw-w64-crt/stdio/ucrt_fprintf.c", - "mingw-w64-crt/stdio/ucrt_fwprintf.c", - "mingw-w64-crt/stdio/ucrt_printf.c", - "mingw-w64-crt/stdio/ucrt_snprintf.c", - "mingw-w64-crt/stdio/ucrt_sprintf.c", - "mingw-w64-crt/stdio/ucrt_vfprintf.c", - "mingw-w64-crt/stdio/ucrt_vprintf.c", - "mingw-w64-crt/stdio/ucrt_vsnprintf.c", - "mingw-w64-crt/stdio/ucrt_vsprintf.c", + var sources []string + if strings.Split(target, "-")[0] == "i386" { + // Old 32-bit x86 systems use msvcrt.dll. + sources = []string{ + "mingw-w64-crt/crt/pseudo-reloc.c", + "mingw-w64-crt/gdtoa/dmisc.c", + "mingw-w64-crt/gdtoa/gdtoa.c", + "mingw-w64-crt/gdtoa/gmisc.c", + "mingw-w64-crt/gdtoa/misc.c", + "mingw-w64-crt/math/x86/exp2.S", + "mingw-w64-crt/math/x86/trunc.S", + "mingw-w64-crt/misc/___mb_cur_max_func.c", + "mingw-w64-crt/misc/lc_locale_func.c", + "mingw-w64-crt/misc/mbrtowc.c", + "mingw-w64-crt/misc/strnlen.c", + "mingw-w64-crt/misc/wcrtomb.c", + "mingw-w64-crt/misc/wcsnlen.c", + "mingw-w64-crt/stdio/acrt_iob_func.c", + "mingw-w64-crt/stdio/mingw_lock.c", + "mingw-w64-crt/stdio/mingw_pformat.c", + "mingw-w64-crt/stdio/mingw_vfprintf.c", + "mingw-w64-crt/stdio/mingw_vsnprintf.c", + } + } else { + // Anything somewhat modern (amd64, arm64) uses UCRT. + sources = []string{ + "mingw-w64-crt/stdio/ucrt_fprintf.c", + "mingw-w64-crt/stdio/ucrt_fwprintf.c", + "mingw-w64-crt/stdio/ucrt_printf.c", + "mingw-w64-crt/stdio/ucrt_snprintf.c", + "mingw-w64-crt/stdio/ucrt_sprintf.c", + "mingw-w64-crt/stdio/ucrt_vfprintf.c", + "mingw-w64-crt/stdio/ucrt_vprintf.c", + "mingw-w64-crt/stdio/ucrt_vsnprintf.c", + "mingw-w64-crt/stdio/ucrt_vsprintf.c", + } } return sources, nil }, @@ -63,27 +100,41 @@ var libMinGW = Library{ func makeMinGWExtraLibs(tmpdir, goarch 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.in", - "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", - } { + var libs []string + if goarch == "386" { + libs = []string{ + // x86 uses msvcrt.dll instead of UCRT for compatibility with old + // Windows versions. + "advapi32.def.in", + "kernel32.def.in", + "msvcrt.def.in", + } + } else { + // Use the modernized UCRT on new systems. + // 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. + libs = []string{ + "advapi32.def.in", + "kernel32.def.in", + "api-ms-win-crt-conio-l1-1-0.def", + "api-ms-win-crt-convert-l1-1-0.def.in", + "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", + } + } + for _, name := range libs { outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib") inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name) job := &compileJob{ @@ -93,6 +144,9 @@ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob { defpath := inpath var archDef, emulation string switch goarch { + case "386": + archDef = "-DDEF_I386" + emulation = "i386pe" case "amd64": archDef = "-DDEF_X64" emulation = "i386pep" diff --git a/compileopts/config.go b/compileopts/config.go index 61efa439fe..d05111f2b0 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -398,15 +398,25 @@ func (c *Config) LibcCFlags() []string { case "mingw-w64": root := goenv.Get("TINYGOROOT") path := c.LibraryPath("mingw-w64") - return []string{ + cflags := []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), - "-D_UCRT", - "-D_WIN32_WINNT=0x0a00", // target Windows 10 } + if c.GOARCH() == "386" { + cflags = append(cflags, + "-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002 + "-D_WIN32_WINNT=0x0501", // target Windows XP + ) + } else { + cflags = append(cflags, + "-D_UCRT", + "-D_WIN32_WINNT=0x0a00", // target Windows 10 + ) + } + return cflags case "": // No libc specified, nothing to add. return nil diff --git a/compileopts/target.go b/compileopts/target.go index 3c5fd62390..7fb3906d38 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -433,14 +433,22 @@ func defaultTarget(options *Options) (*TargetSpec, error) { spec.Scheduler = "tasks" spec.Linker = "ld.lld" spec.Libc = "mingw-w64" - // Note: using a medium code model, low image base and no ASLR - // because Go doesn't really need those features. ASLR patches - // around issues for unsafe languages like C/C++ that are not - // normally present in Go (without explicitly opting in). - // For more discussion: - // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 switch options.GOARCH { + case "386": + spec.LDFlags = append(spec.LDFlags, + "-m", "i386pe", + "--major-os-version", "4", + "--major-subsystem-version", "4", + ) + // __udivdi3 is not present in ucrt it seems. + spec.RTLib = "compiler-rt" case "amd64": + // Note: using a medium code model, low image base and no ASLR + // because Go doesn't really need those features. ASLR patches + // around issues for unsafe languages like C/C++ that are not + // normally present in Go (without explicitly opting in). + // For more discussion: + // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 spec.LDFlags = append(spec.LDFlags, "-m", "i386pep", "--image-base", "0x400000", diff --git a/compiler/calls.go b/compiler/calls.go index 6400e634bd..a44ac38a87 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -76,7 +76,15 @@ func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value, fragments := b.expandFormalParam(arg) expanded = append(expanded, fragments...) } - return b.CreateCall(fnType, fn, expanded, name) + call := b.CreateCall(fnType, fn, expanded, name) + if !fn.IsAFunction().IsNil() { + if cc := fn.FunctionCallConv(); cc != llvm.CCallConv { + // Set a different calling convention if needed. + // This is needed for GetModuleHandleExA on Windows, for example. + call.SetInstructionCallConv(cc) + } + } + return call } // createInvoke is like createCall but continues execution at the landing pad if diff --git a/compiler/llvm.go b/compiler/llvm.go index 139c5a1cd8..de387b39c0 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -452,6 +452,21 @@ func (b *builder) readStackPointer() llvm.Value { return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "") } +// writeStackPointer emits a LLVM intrinsic call that updates the current stack +// pointer. +func (b *builder) writeStackPointer(sp llvm.Value) { + name := "llvm.stackrestore.p0" + if llvmutil.Version() < 18 { + name = "llvm.stackrestore" // backwards compatibility with LLVM 17 and below + } + stackrestore := b.mod.NamedFunction(name) + if stackrestore.IsNil() { + fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false) + stackrestore = llvm.AddFunction(b.mod, name, fnType) + } + b.CreateCall(stackrestore.GlobalValueType(), stackrestore, []llvm.Value{sp}, "") +} + // createZExtOrTrunc lets the input value fit in the output type bits, by zero // extending or truncating the integer. func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value { diff --git a/compiler/symbol.go b/compiler/symbol.go index d3b8069a2a..c56e0792f2 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -208,6 +208,12 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // > circumstances, and should not be exposed to source languages. llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn) } + case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryPerformanceCounter", "QueryPerformanceFrequency", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "SystemFunction036", "VirtualAlloc": + // On Windows we need to use a special calling convention for some + // external calls. + if c.GOOS == "windows" && c.GOARCH == "386" { + llvmFn.SetFunctionCallConv(llvm.X86StdcallCallConv) + } } // External/exported functions may not retain pointer values. diff --git a/compiler/syscall.go b/compiler/syscall.go index aa40ad1a55..7fd6e354c6 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -268,6 +268,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { // The signature looks like this: // func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) + isI386 := strings.HasPrefix(b.Triple, "i386-") + // Prepare input values. var paramTypes []llvm.Type var params []llvm.Value @@ -285,11 +287,17 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { if setLastError.IsNil() { llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false) setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType) + if isI386 { + setLastError.SetFunctionCallConv(llvm.X86StdcallCallConv) + } } getLastError := b.mod.NamedFunction("GetLastError") if getLastError.IsNil() { llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false) getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType) + if isI386 { + getLastError.SetFunctionCallConv(llvm.X86StdcallCallConv) + } } // Now do the actual call. Pseudocode: @@ -300,9 +308,24 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { // 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.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "") + // On windows/386, we also need to save/restore the stack pointer. I'm + // not entirely sure why this is needed, but without it these calls + // change the stack pointer leading to a crash soon after. + setLastErrorCall := b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "") + var sp llvm.Value + if isI386 { + setLastErrorCall.SetInstructionCallConv(llvm.X86StdcallCallConv) + sp = b.readStackPointer() + } syscallResult := b.CreateCall(llvmType, fnPtr, params, "") + if isI386 { + syscallResult.SetInstructionCallConv(llvm.X86StdcallCallConv) + b.writeStackPointer(sp) + } errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err") + if isI386 { + errResult.SetInstructionCallConv(llvm.X86StdcallCallConv) + } if b.uintptrType != b.ctx.Int32Type() { errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr") } diff --git a/main_test.go b/main_test.go index f193f46799..d950340b24 100644 --- a/main_test.go +++ b/main_test.go @@ -35,6 +35,8 @@ const TESTDATA = "testdata" var testTarget = flag.String("target", "", "override test target") +var testOnlyCurrentOS = flag.Bool("only-current-os", false, "") + var supportedLinuxArches = map[string]string{ "AMD64Linux": "linux/amd64", "X86Linux": "linux/386", @@ -158,20 +160,35 @@ func TestBuild(t *testing.T) { return } - t.Run("EmulatedCortexM3", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t) - }) + if !*testOnlyCurrentOS { + t.Run("EmulatedCortexM3", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t) + }) - t.Run("EmulatedRISCV", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t) - }) + t.Run("EmulatedRISCV", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t) + }) - t.Run("AVR", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("simavr", sema), tests, t) - }) + t.Run("AVR", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("simavr", sema), tests, t) + }) + + t.Run("WebAssembly", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("wasm", sema), tests, t) + }) + t.Run("WASI", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("wasip1", sema), tests, t) + }) + t.Run("WASIp2", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("wasip2", sema), tests, t) + }) + } if runtime.GOOS == "linux" { for name, osArch := range supportedLinuxArches { @@ -191,18 +208,13 @@ func TestBuild(t *testing.T) { options := optionsFromOSARCH("linux/mipsle/softfloat", sema) runTest("cgo/", options, t, nil, nil) }) - t.Run("WebAssembly", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("wasm", sema), tests, t) - }) - t.Run("WASI", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("wasip1", sema), tests, t) - }) - t.Run("WASIp2", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("wasip2", sema), tests, t) - }) + } else if runtime.GOOS == "windows" { + if runtime.GOARCH != "386" { + t.Run("Windows386", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromOSARCH("windows/386", sema), tests, t) + }) + } } } diff --git a/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go index 16266198fe..d8c9173c1e 100644 --- a/src/crypto/rand/rand_windows.go +++ b/src/crypto/rand/rand_windows.go @@ -1,6 +1,9 @@ package rand -import "errors" +import ( + "errors" + "unsafe" +) func init() { Reader = &reader{} @@ -16,28 +19,22 @@ func (r *reader) Read(b []byte) (n int, err error) { 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) + // Use the old RtlGenRandom, introduced in Windows XP. + // Even though the documentation says it is deprecated, it is widely used + // and probably won't go away anytime soon. + // See for example: https://github.com/golang/go/issues/33542 + // For Windows 7 and newer, we might switch to ProcessPrng in the future + // (which is a documented function and might be a tiny bit faster). + ok := libc_RtlGenRandom(unsafe.Pointer(&b[0]), len(b)) + if !ok { + return 0, errRandom } - 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); +// This function is part of advapi32.dll, and is called SystemFunction036 for +// some reason. It's available on Windows XP and newer. +// See: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom // -//export rand_s -func libc_rand_s(randomValue *uint32) int32 +//export SystemFunction036 +func libc_RtlGenRandom(buf unsafe.Pointer, len int) bool diff --git a/src/internal/task/task_stack_386.S b/src/internal/task/task_stack_386.S index 402e9e50f0..f92dae0bf3 100644 --- a/src/internal/task/task_stack_386.S +++ b/src/internal/task/task_stack_386.S @@ -1,7 +1,12 @@ +#ifdef _WIN32 +.global _tinygo_startTask +_tinygo_startTask: +#else // Linux etc .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: +#endif .cfi_startproc // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. @@ -24,12 +29,21 @@ tinygo_startTask: addl $4, %esp // After return, exit this goroutine. This is a tail call. + #ifdef _WIN32 + jmp _tinygo_task_exit + #else jmp tinygo_task_exit + #endif .cfi_endproc +#ifdef _WIN32 +.global _tinygo_swapTask +_tinygo_swapTask: +#else .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: +#endif // This function gets the following parameters: movl 4(%esp), %eax // newStack uintptr movl 8(%esp), %ecx // oldStack *uintptr diff --git a/src/runtime/asm_386.S b/src/runtime/asm_386.S index faaa7c3a3b..f463ffa0a0 100644 --- a/src/runtime/asm_386.S +++ b/src/runtime/asm_386.S @@ -1,7 +1,12 @@ +#ifdef _WIN32 +.global _tinygo_scanCurrentStack +_tinygo_scanCurrentStack: +#else .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack .type tinygo_scanCurrentStack, %function tinygo_scanCurrentStack: +#endif // Sources: // * https://stackoverflow.com/questions/18024672/what-registers-are-preserved-through-a-linux-x86-64-function-call // * https://godbolt.org/z/q7e8dn @@ -15,7 +20,11 @@ tinygo_scanCurrentStack: // Scan the stack. subl $8, %esp // adjust the stack before the call to maintain 16-byte alignment pushl %esp + #ifdef _WIN32 + calll _tinygo_scanstack + #else calll tinygo_scanstack + #endif // Restore the stack pointer. Registers do not need to be restored as they // were only pushed to be discoverable by the GC. @@ -23,9 +32,14 @@ tinygo_scanCurrentStack: retl +#ifdef _WIN32 +.global _tinygo_longjmp +_tinygo_longjmp: +#else .section .text.tinygo_longjmp .global tinygo_longjmp tinygo_longjmp: +#endif // Note: the code we jump to assumes eax is set to a non-zero value if we // jump from here. movl 4(%esp), %eax diff --git a/src/runtime/runtime_windows.go b/src/runtime/runtime_windows.go index 17ee2ce721..c4df8dec36 100644 --- a/src/runtime/runtime_windows.go +++ b/src/runtime/runtime_windows.go @@ -42,10 +42,24 @@ func __p___argc() *int32 //export __p___argv func __p___argv() **unsafe.Pointer +type startupInfo struct { + newMode int32 +} + +//export __getmainargs +func __getmainargs(argc *int32, argv, env **unsafe.Pointer, doWildcard int, startInfo *startupInfo) int32 + +var performanceFrequency int64 + //export mainCRTStartup func mainCRTStartup() int { preinit() + // Obtain the (constant) performance frequency when needed. + if GOARCH == "386" { + _QueryPerformanceFrequency(&performanceFrequency) + } + // 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. @@ -78,9 +92,19 @@ var args []string func os_runtime_args() []string { if args == nil { // Obtain argc/argv from the environment. - _configure_narrow_argv(2) - argc := *__p___argc() - argv := *__p___argv() + var argc int32 + var argv *unsafe.Pointer + if GOARCH == "386" { + // MSVCRT.DLL + var env *unsafe.Pointer + startInfo := startupInfo{newMode: 0} + __getmainargs(&argc, &argv, &env, 1, &startInfo) + } else { + // UCRT + _configure_narrow_argv(2) + argc = *__p___argc() + argv = *__p___argv() + } // Make args slice big enough so that it can store all command line // arguments. @@ -146,10 +170,30 @@ func sleepTicks(d timeUnit) { } } +//export QueryPerformanceFrequency +func _QueryPerformanceFrequency(*int64) bool + +//export QueryPerformanceCounter +func _QueryPerformanceCounter(*int64) bool + func ticks() timeUnit { - var unbiasedTime uint64 - _QueryUnbiasedInterruptTime(&unbiasedTime) - return timeUnit(unbiasedTime) + if GOARCH == "386" { + // Unfortunately QueryUnbiasedInterruptTime is only available starting + // with Windows 7. + + // Obtain counter (that runs at a fixed frequency). + var counter int64 + _QueryPerformanceCounter(&counter) + // Convert this counter to ticks of 100ns (just like + // QueryUnbiasedInterruptTime). + // (We could also change the definition of ticks on GOOS=386 but that + // seems messy). + return timeUnit((counter * 10000000) / performanceFrequency) + } else { + var unbiasedTime uint64 + _QueryUnbiasedInterruptTime(&unbiasedTime) + return timeUnit(unbiasedTime) + } } //go:linkname now time.now @@ -237,17 +281,16 @@ func procUnpin() { } func hardwareRand() (n uint64, ok bool) { - var n1, n2 uint32 - errCode1 := libc_rand_s(&n1) - errCode2 := libc_rand_s(&n2) - n = uint64(n1)<<32 | uint64(n2) - ok = errCode1 == 0 && errCode2 == 0 + // Use the old RtlGenRandom, introduced in Windows XP. + // See the rationale in src/crypto/rand/rand_windows.go for why we use this + // one. + ok = _RtlGenRandom(unsafe.Pointer(&n), 8) return } -// 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); +// This function is part of advapi32.dll, and is called SystemFunction036 for +// some reason. It's available on Windows XP and newer. +// See: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom // -//export rand_s -func libc_rand_s(randomValue *uint32) int32 +//export SystemFunction036 +func _RtlGenRandom(buf unsafe.Pointer, len int) bool