mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-18 19:44:00 +00:00
windows: add windows/386 support
This commit is contained in:
@@ -101,7 +101,7 @@ jobs:
|
|||||||
- name: make gen-device
|
- name: make gen-device
|
||||||
run: make -j3 gen-device
|
run: make -j3 gen-device
|
||||||
- name: Test TinyGo
|
- name: Test TinyGo
|
||||||
run: make test GOTESTFLAGS="-short"
|
run: make test GOTESTFLAGS="-only-current-os"
|
||||||
- name: Build TinyGo release tarball
|
- name: Build TinyGo release tarball
|
||||||
run: make release -j3
|
run: make release -j3
|
||||||
- name: Test stdlib packages
|
- name: Test stdlib packages
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
run: make -j3 gen-device
|
run: make -j3 gen-device
|
||||||
- name: Test TinyGo
|
- name: Test TinyGo
|
||||||
shell: bash
|
shell: bash
|
||||||
run: make test GOTESTFLAGS="-short"
|
run: make test GOTESTFLAGS="-only-current-os"
|
||||||
- name: Build TinyGo release tarball
|
- name: Build TinyGo release tarball
|
||||||
shell: bash
|
shell: bash
|
||||||
run: make build/release -j4
|
run: make build/release -j4
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ func TestClangAttributes(t *testing.T) {
|
|||||||
{GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"},
|
{GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"},
|
||||||
{GOOS: "darwin", GOARCH: "amd64"},
|
{GOOS: "darwin", GOARCH: "amd64"},
|
||||||
{GOOS: "darwin", GOARCH: "arm64"},
|
{GOOS: "darwin", GOARCH: "arm64"},
|
||||||
|
{GOOS: "windows", GOARCH: "386"},
|
||||||
{GOOS: "windows", GOARCH: "amd64"},
|
{GOOS: "windows", GOARCH: "amd64"},
|
||||||
{GOOS: "windows", GOARCH: "arm64"},
|
{GOOS: "windows", GOARCH: "arm64"},
|
||||||
} {
|
} {
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
|
|||||||
defpath := inpath
|
defpath := inpath
|
||||||
var archDef, emulation string
|
var archDef, emulation string
|
||||||
switch goarch {
|
switch goarch {
|
||||||
|
case "386":
|
||||||
|
archDef = "-DDEF_I386"
|
||||||
|
emulation = "i386pe"
|
||||||
case "amd64":
|
case "amd64":
|
||||||
archDef = "-DDEF_X64"
|
archDef = "-DDEF_X64"
|
||||||
emulation = "i386pep"
|
emulation = "i386pep"
|
||||||
|
|||||||
+12
-6
@@ -430,14 +430,20 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
|||||||
spec.GC = "boehm"
|
spec.GC = "boehm"
|
||||||
spec.Linker = "ld.lld"
|
spec.Linker = "ld.lld"
|
||||||
spec.Libc = "mingw-w64"
|
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 {
|
switch options.GOARCH {
|
||||||
|
case "386":
|
||||||
|
spec.LDFlags = append(spec.LDFlags,
|
||||||
|
"-m", "i386pe",
|
||||||
|
)
|
||||||
|
// __udivdi3 is not present in ucrt it seems.
|
||||||
|
spec.RTLib = "compiler-rt"
|
||||||
case "amd64":
|
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,
|
spec.LDFlags = append(spec.LDFlags,
|
||||||
"-m", "i386pep",
|
"-m", "i386pep",
|
||||||
"--image-base", "0x400000",
|
"--image-base", "0x400000",
|
||||||
|
|||||||
+9
-1
@@ -76,7 +76,15 @@ func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value,
|
|||||||
fragments := b.expandFormalParam(arg)
|
fragments := b.expandFormalParam(arg)
|
||||||
expanded = append(expanded, fragments...)
|
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
|
// createInvoke is like createCall but continues execution at the landing pad if
|
||||||
|
|||||||
@@ -452,6 +452,21 @@ func (b *builder) readStackPointer() llvm.Value {
|
|||||||
return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "")
|
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
|
// createZExtOrTrunc lets the input value fit in the output type bits, by zero
|
||||||
// extending or truncating the integer.
|
// extending or truncating the integer.
|
||||||
func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value {
|
func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value {
|
||||||
|
|||||||
@@ -208,6 +208,12 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
|||||||
// > circumstances, and should not be exposed to source languages.
|
// > circumstances, and should not be exposed to source languages.
|
||||||
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
|
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
|
||||||
}
|
}
|
||||||
|
case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "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.
|
// External/exported functions may not retain pointer values.
|
||||||
|
|||||||
+24
-1
@@ -268,6 +268,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||||||
// The signature looks like this:
|
// The signature looks like this:
|
||||||
// func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
|
// func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
|
||||||
|
|
||||||
|
isI386 := strings.HasPrefix(b.Triple, "i386-")
|
||||||
|
|
||||||
// Prepare input values.
|
// Prepare input values.
|
||||||
var paramTypes []llvm.Type
|
var paramTypes []llvm.Type
|
||||||
var params []llvm.Value
|
var params []llvm.Value
|
||||||
@@ -285,11 +287,17 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||||||
if setLastError.IsNil() {
|
if setLastError.IsNil() {
|
||||||
llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
|
llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
|
||||||
setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
|
setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
|
||||||
|
if isI386 {
|
||||||
|
setLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
getLastError := b.mod.NamedFunction("GetLastError")
|
getLastError := b.mod.NamedFunction("GetLastError")
|
||||||
if getLastError.IsNil() {
|
if getLastError.IsNil() {
|
||||||
llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
|
llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
|
||||||
getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
|
getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
|
||||||
|
if isI386 {
|
||||||
|
getLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now do the actual call. Pseudocode:
|
// 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
|
// Note that SetLastError/GetLastError could be replaced with direct
|
||||||
// access to the thread control block, which is probably smaller and
|
// access to the thread control block, which is probably smaller and
|
||||||
// faster. The Go runtime does this in assembly.
|
// 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, "")
|
syscallResult := b.CreateCall(llvmType, fnPtr, params, "")
|
||||||
|
if isI386 {
|
||||||
|
syscallResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
|
||||||
|
b.writeStackPointer(sp)
|
||||||
|
}
|
||||||
errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err")
|
errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err")
|
||||||
|
if isI386 {
|
||||||
|
errResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
|
||||||
|
}
|
||||||
if b.uintptrType != b.ctx.Int32Type() {
|
if b.uintptrType != b.ctx.Int32Type() {
|
||||||
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
|
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-24
@@ -35,6 +35,8 @@ const TESTDATA = "testdata"
|
|||||||
|
|
||||||
var testTarget = flag.String("target", "", "override test target")
|
var testTarget = flag.String("target", "", "override test target")
|
||||||
|
|
||||||
|
var testOnlyCurrentOS = flag.Bool("only-current-os", false, "")
|
||||||
|
|
||||||
var supportedLinuxArches = map[string]string{
|
var supportedLinuxArches = map[string]string{
|
||||||
"AMD64Linux": "linux/amd64",
|
"AMD64Linux": "linux/amd64",
|
||||||
"X86Linux": "linux/386",
|
"X86Linux": "linux/386",
|
||||||
@@ -158,20 +160,35 @@ func TestBuild(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("EmulatedCortexM3", func(t *testing.T) {
|
if !*testOnlyCurrentOS {
|
||||||
t.Parallel()
|
t.Run("EmulatedCortexM3", func(t *testing.T) {
|
||||||
runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t)
|
t.Parallel()
|
||||||
})
|
runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("EmulatedRISCV", func(t *testing.T) {
|
t.Run("EmulatedRISCV", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t)
|
runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("AVR", func(t *testing.T) {
|
t.Run("AVR", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
runPlatTests(optionsFromTarget("simavr", sema), tests, t)
|
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" {
|
if runtime.GOOS == "linux" {
|
||||||
for name, osArch := range supportedLinuxArches {
|
for name, osArch := range supportedLinuxArches {
|
||||||
@@ -191,18 +208,13 @@ func TestBuild(t *testing.T) {
|
|||||||
options := optionsFromOSARCH("linux/mipsle/softfloat", sema)
|
options := optionsFromOSARCH("linux/mipsle/softfloat", sema)
|
||||||
runTest("cgo/", options, t, nil, nil)
|
runTest("cgo/", options, t, nil, nil)
|
||||||
})
|
})
|
||||||
t.Run("WebAssembly", func(t *testing.T) {
|
} else if runtime.GOOS == "windows" {
|
||||||
t.Parallel()
|
if runtime.GOARCH != "386" {
|
||||||
runPlatTests(optionsFromTarget("wasm", sema), tests, t)
|
t.Run("Windows386", func(t *testing.T) {
|
||||||
})
|
t.Parallel()
|
||||||
t.Run("WASI", func(t *testing.T) {
|
runPlatTests(optionsFromOSARCH("windows/386", sema), tests, t)
|
||||||
t.Parallel()
|
})
|
||||||
runPlatTests(optionsFromTarget("wasip1", sema), tests, t)
|
}
|
||||||
})
|
|
||||||
t.Run("WASIp2", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
runPlatTests(optionsFromTarget("wasip2", sema), tests, t)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
#ifdef _WIN32
|
||||||
|
.global _tinygo_startTask
|
||||||
|
_tinygo_startTask:
|
||||||
|
#else // Linux etc
|
||||||
.section .text.tinygo_startTask
|
.section .text.tinygo_startTask
|
||||||
.global tinygo_startTask
|
.global tinygo_startTask
|
||||||
.type tinygo_startTask, %function
|
.type tinygo_startTask, %function
|
||||||
tinygo_startTask:
|
tinygo_startTask:
|
||||||
|
#endif
|
||||||
.cfi_startproc
|
.cfi_startproc
|
||||||
// Small assembly stub for starting a goroutine. This is already run on the
|
// Small assembly stub for starting a goroutine. This is already run on the
|
||||||
// new stack, with the callee-saved registers already loaded.
|
// new stack, with the callee-saved registers already loaded.
|
||||||
@@ -24,12 +29,21 @@ tinygo_startTask:
|
|||||||
addl $4, %esp
|
addl $4, %esp
|
||||||
|
|
||||||
// After return, exit this goroutine. This is a tail call.
|
// After return, exit this goroutine. This is a tail call.
|
||||||
|
#ifdef _WIN32
|
||||||
|
jmp _tinygo_pause
|
||||||
|
#else
|
||||||
jmp tinygo_pause
|
jmp tinygo_pause
|
||||||
|
#endif
|
||||||
.cfi_endproc
|
.cfi_endproc
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
.global _tinygo_swapTask
|
||||||
|
_tinygo_swapTask:
|
||||||
|
#else
|
||||||
.global tinygo_swapTask
|
.global tinygo_swapTask
|
||||||
.type tinygo_swapTask, %function
|
.type tinygo_swapTask, %function
|
||||||
tinygo_swapTask:
|
tinygo_swapTask:
|
||||||
|
#endif
|
||||||
// This function gets the following parameters:
|
// This function gets the following parameters:
|
||||||
movl 4(%esp), %eax // newStack uintptr
|
movl 4(%esp), %eax // newStack uintptr
|
||||||
movl 8(%esp), %ecx // oldStack *uintptr
|
movl 8(%esp), %ecx // oldStack *uintptr
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
#ifdef _WIN32
|
||||||
|
.global _tinygo_scanCurrentStack
|
||||||
|
_tinygo_scanCurrentStack:
|
||||||
|
#else
|
||||||
.section .text.tinygo_scanCurrentStack
|
.section .text.tinygo_scanCurrentStack
|
||||||
.global tinygo_scanCurrentStack
|
.global tinygo_scanCurrentStack
|
||||||
.type tinygo_scanCurrentStack, %function
|
.type tinygo_scanCurrentStack, %function
|
||||||
tinygo_scanCurrentStack:
|
tinygo_scanCurrentStack:
|
||||||
|
#endif
|
||||||
// Sources:
|
// Sources:
|
||||||
// * https://stackoverflow.com/questions/18024672/what-registers-are-preserved-through-a-linux-x86-64-function-call
|
// * https://stackoverflow.com/questions/18024672/what-registers-are-preserved-through-a-linux-x86-64-function-call
|
||||||
// * https://godbolt.org/z/q7e8dn
|
// * https://godbolt.org/z/q7e8dn
|
||||||
@@ -15,7 +20,11 @@ tinygo_scanCurrentStack:
|
|||||||
// Scan the stack.
|
// Scan the stack.
|
||||||
subl $8, %esp // adjust the stack before the call to maintain 16-byte alignment
|
subl $8, %esp // adjust the stack before the call to maintain 16-byte alignment
|
||||||
pushl %esp
|
pushl %esp
|
||||||
|
#ifdef _WIN32
|
||||||
|
calll _tinygo_scanstack
|
||||||
|
#else
|
||||||
calll tinygo_scanstack
|
calll tinygo_scanstack
|
||||||
|
#endif
|
||||||
|
|
||||||
// Restore the stack pointer. Registers do not need to be restored as they
|
// Restore the stack pointer. Registers do not need to be restored as they
|
||||||
// were only pushed to be discoverable by the GC.
|
// were only pushed to be discoverable by the GC.
|
||||||
@@ -23,9 +32,14 @@ tinygo_scanCurrentStack:
|
|||||||
retl
|
retl
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
.global _tinygo_longjmp
|
||||||
|
_tinygo_longjmp:
|
||||||
|
#else
|
||||||
.section .text.tinygo_longjmp
|
.section .text.tinygo_longjmp
|
||||||
.global tinygo_longjmp
|
.global tinygo_longjmp
|
||||||
tinygo_longjmp:
|
tinygo_longjmp:
|
||||||
|
#endif
|
||||||
// Note: the code we jump to assumes eax is set to a non-zero value if we
|
// Note: the code we jump to assumes eax is set to a non-zero value if we
|
||||||
// jump from here.
|
// jump from here.
|
||||||
movl 4(%esp), %eax
|
movl 4(%esp), %eax
|
||||||
|
|||||||
Reference in New Issue
Block a user