diff --git a/loader/goroot.go b/loader/goroot.go index 5442df9b5d..ca56c4d49b 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -45,7 +45,7 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) { } // Find the overrides needed for the goroot. - overrides := pathsToOverride(config.GoMinorVersion, needsSyscallPackage(config.BuildTags())) + overrides := pathsToOverride(config.GoMinorVersion, config.BuildTags()) // Resolve the merge links within the goroot. merge, err := listGorootMergeLinks(goroot, tinygoroot, overrides) @@ -225,9 +225,28 @@ func needsSyscallPackage(buildTags []string) bool { return false } -// The boolean indicates whether to merge the subdirs. True means merge, false -// means use the TinyGo version. -func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { +// linuxNetworking returns whether the unmodified go linux net stack should be used +// until the full rework of the net package is done. +// To ensure the correct build target, check for the following tags: +// linux && !baremetal && !nintendoswitch && !tinygo.wasm +func linuxNetworking(buildTags []string) bool { + targetLinux := false + for _, tag := range buildTags { + if tag == "linux" { + targetLinux = true + } + if tag == "baremetal" || tag == "nintendoswitch" || tag == "tinygo.wasm" { + return false + } + } + return targetLinux +} + +// The boolean indicates whether to merge the subdirs. +// +// True: Merge the golang and tinygo source directories. +// False: Uses the TinyGo version exclusively. +func pathsToOverride(goMinor int, buildTags []string) map[string]bool { paths := map[string]bool{ "": true, "crypto/": true, @@ -250,7 +269,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "internal/task/": false, "internal/wasi/": false, "machine/": false, - "net/": true, + "net/": true, // this is important if the GOOS != linux "net/http/": false, "os/": true, "reflect/": false, @@ -267,9 +286,18 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { paths["crypto/internal/boring/sig/"] = false } - if needsSyscallPackage { + if needsSyscallPackage(buildTags) { paths["syscall/"] = true // include syscall/js } + + // To make sure the correct version of the net package is used, it is advised + // to clean the go cache before building + if linuxNetworking(buildTags) { + for _, v := range []string{"crypto/tls/", "net/http/", "net/"} { + delete(paths, v) // remote entries so go stdlib is used + } + } + return paths } diff --git a/loader/loader.go b/loader/loader.go index e935a9de3a..f94fc70f8e 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -279,7 +279,7 @@ func (p *Program) getOriginalPath(path string) string { originalPath = realgorootPath } maybeInTinyGoRoot := false - for prefix := range pathsToOverride(p.config.GoMinorVersion, needsSyscallPackage(p.config.BuildTags())) { + for prefix := range pathsToOverride(p.config.GoMinorVersion, p.config.BuildTags()) { if runtime.GOOS == "windows" { prefix = strings.ReplaceAll(prefix, "/", "\\") } diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go new file mode 100644 index 0000000000..e4157b994f --- /dev/null +++ b/src/runtime/netpoll.go @@ -0,0 +1,47 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux + +package runtime + +// For debugging purposes this is used for all target architectures, but this is only valid for linux systems. +// TODO: add linux specific build tags +type pollDesc struct { + runtimeCtx uintptr +} + +func (pd *pollDesc) wait(mode int, isFile bool) error { + return nil +} + +const ( + pollNoError = 0 // no error + pollErrClosing = 1 // descriptor is closed + pollErrTimeout = 2 // I/O timeout + pollErrNotPollable = 3 // general error polling descriptor +) + +//go:linkname poll_runtime_pollReset internal/poll.runtime_pollReset +func poll_runtime_pollReset(pd *pollDesc, mode int) int { + // println("poll_runtime_pollReset not implemented", pd, mode) + return pollNoError +} + +//go:linkname poll_runtime_pollWait internal/poll.runtime_pollWait +func poll_runtime_pollWait(pd *pollDesc, mode int) int { + // println("poll_runtime_pollWait not implemented", pd, mode) + return pollNoError +} + +//go:linkname poll_runtime_pollSetDeadline internal/poll.runtime_pollSetDeadline +func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { + // println("poll_runtime_pollSetDeadline not implemented", pd, d, mode) +} + +//go:linkname poll_runtime_pollOpen internal/poll.runtime_pollOpen +func poll_runtime_pollOpen(fd uintptr) (*pollDesc, int) { + // println("poll_runtime_pollOpen not implemented", fd) + return &pollDesc{runtimeCtx: uintptr(0x1337)}, pollNoError +} diff --git a/src/runtime/netpoll_generic.go b/src/runtime/netpoll_generic.go new file mode 100644 index 0000000000..f5f26d84d9 --- /dev/null +++ b/src/runtime/netpoll_generic.go @@ -0,0 +1,38 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux + +package runtime + +const ( + pollNoError = 0 // no error + pollErrClosing = 1 // descriptor is closed + pollErrTimeout = 2 // I/O timeout + pollErrNotPollable = 3 // general error polling descriptor +) + +// Network poller descriptor. +// +// No heap pointers. +// For linux to call create Fds with a pollDesc, it needs a ctxRuntime pointer, so use the original pollDesc struct. +// On linux we have a heap. +type pollDesc struct{} + +//go:linkname poll_runtime_pollReset internal/poll.runtime_pollReset +func poll_runtime_pollReset(pd *pollDesc, mode int) int { + println("poll_runtime_pollReset not implemented", pd, mode) + return pollErrClosing +} + +//go:linkname poll_runtime_pollWait internal/poll.runtime_pollWait +func poll_runtime_pollWait(pd *pollDesc, mode int) int { + println("poll_runtime_pollWait not implemented", pd, mode) + return pollErrClosing +} + +//go:linkname poll_runtime_pollSetDeadline internal/poll.runtime_pollSetDeadline +func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { + println("poll_runtime_pollSetDeadline not implemented", pd, d, mode) +} diff --git a/src/runtime/poll.go b/src/runtime/poll.go index 2713bbea7e..a880272c87 100644 --- a/src/runtime/poll.go +++ b/src/runtime/poll.go @@ -4,20 +4,21 @@ package runtime //go:linkname poll_runtime_pollServerInit internal/poll.runtime_pollServerInit func poll_runtime_pollServerInit() { - panic("todo: runtime_pollServerInit") + // fmt.Printf("poll_runtime_pollServerInit not implemented, skipping panic\n") } -//go:linkname poll_runtime_pollOpen internal/poll.runtime_pollOpen -func poll_runtime_pollOpen(fd uintptr) (uintptr, int) { - panic("todo: runtime_pollOpen") -} +// //go:linkname poll_runtime_pollOpen internal/poll.runtime_pollOpen +// func poll_runtime_pollOpen(fd uintptr) (uintptr, int) { +// // fmt.Printf("poll_runtime_pollOpen not implemented, skipping panic\n") +// return 0, 0 +// } //go:linkname poll_runtime_pollClose internal/poll.runtime_pollClose func poll_runtime_pollClose(ctx uintptr) { - panic("todo: runtime_pollClose") + // fmt.Printf("poll_runtime_pollClose not implemented, skipping panic\n") } //go:linkname poll_runtime_pollUnblock internal/poll.runtime_pollUnblock func poll_runtime_pollUnblock(ctx uintptr) { - panic("todo: runtime_pollUnblock") + // fmt.Printf("poll_runtime_pollUnblock not implemented, skipping panic\n") } diff --git a/src/runtime/sync.go b/src/runtime/sync.go index a0851401a0..bf0499fe9e 100644 --- a/src/runtime/sync.go +++ b/src/runtime/sync.go @@ -1,13 +1,52 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package runtime +import ( + "internal/futex" +) + // This file contains stub implementations for internal/poll. +// The official golang implementation states: +// +// "That is, don't think of these as semaphores. +// Think of them as a way to implement sleep and wakeup +// such that every sleep is paired with a single wakeup, +// even if, due to races, the wakeup happens before the sleep." +// +// This is an experimental and probably incomplete implementation of the +// semaphore system, tailed to the network use case. That means, that it does not +// implement the modularity that the semacquire/semacquire1 implementation model +// offers, which in fact is emitted here entirely. +// This means we assume the following constant settings from the golang standard +// library: lifo=false,profile=semaBlock,skipframe=0,reason=waitReasonSemaquire //go:linkname semacquire internal/poll.runtime_Semacquire func semacquire(sema *uint32) { - panic("todo: semacquire") + var semaBlock futex.Futex + semaBlock.Store(*sema) + + // check if we can acquire the semaphore + semaBlock.Wait(1) + + // the semaphore is free to use so we can acquire it + if semaBlock.Swap(0) != 1 { + panic("semaphore is already acquired, racy") + } } //go:linkname semrelease internal/poll.runtime_Semrelease func semrelease(sema *uint32) { - panic("todo: semrelease") + var semaBlock futex.Futex + semaBlock.Store(*sema) + + // check if we can release the semaphore + if semaBlock.Swap(1) != 0 { + panic("semaphore is not acquired, racy") + } + + // wake up the next waiter + semaBlock.Wake() } diff --git a/src/syscall/forklock.go b/src/syscall/forklock.go new file mode 100644 index 0000000000..a158b24871 --- /dev/null +++ b/src/syscall/forklock.go @@ -0,0 +1,17 @@ +//go:build tinygo && linux && !wasip1 && !wasip2 && tinygo.wasm && !wasm_unknown && !darwin && !baremetal && !nintendoswitch + +package syscall + +import ( + "sync" +) + +var ForkLock sync.RWMutex + +func CloseOnExec(fd int) { + system.CloseOnExec(fd) +} + +func SetNonblock(fd int, nonblocking bool) (err error) { + return system.SetNonblock(fd, nonblocking) +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 0ef9784283..5cacc0c8bb 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -215,7 +215,11 @@ func Truncate(path string, length int64) (err error) { func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) func Kill(pid int, sig Signal) (err error) { - return ENOSYS // TODO + result := libc_kill(int32(pid), int32(sig)) + if result < 0 { + err = getErrno() + } + return } type SysProcAttr struct{} @@ -418,3 +422,8 @@ func libc_execve(filename *byte, argv **byte, envp **byte) int // //export truncate func libc_truncate(path *byte, length int64) int32 + +// int kill(pid_t pid, int sig); +// +//export kill +func libc_kill(pid, sig int32) int32 diff --git a/testdata/net.go b/testdata/net.go new file mode 100644 index 0000000000..2a7908fa1c --- /dev/null +++ b/testdata/net.go @@ -0,0 +1,73 @@ +package main + +import ( + "io" + "net" + "strconv" +) + +// Test golang network package integration for tinygo. +// This test is not exhaustive and only tests the basic functionality of the package. + +var ( + testsPassed uint + lnPort int + err error + recvBuf []byte +) + +var ( + testDialListenData = []byte("Hello tinygo :)") +) + +func TestDialListen() { + // listen thread + listenReady := make(chan bool, 1) + go func() { + ln, err := net.Listen("tcp4", ":0") + if err != nil { + println("error listening: ", err) + return + } + lnPort = ln.Addr().(*net.TCPAddr).Port + + listenReady <- true + conn, err := ln.Accept() + if err != nil { + println("error accepting:", err) + return + } + + recvBuf = make([]byte, len(testDialListenData)) + if _, err := io.ReadFull(conn, recvBuf); err != nil { + println("error reading: ", err) + return + } + + if string(recvBuf) != string(testDialListenData) { + println("error: received data does not match sent data", string(recvBuf), " != ", string(testDialListenData)) + return + } + conn.Close() + + return + }() + + <-listenReady + conn, err := net.Dial("tcp4", "127.0.0.1:"+strconv.FormatInt(int64(lnPort), 10)) + if err != nil { + println("error dialing: ", err) + return + } + + if _, err = conn.Write(testDialListenData); err != nil { + println("error writing: ", err) + return + } +} + +func main() { + println("test: net start") + TestDialListen() + println("test: net end") +} diff --git a/testdata/net.txt b/testdata/net.txt new file mode 100644 index 0000000000..82681e45a0 --- /dev/null +++ b/testdata/net.txt @@ -0,0 +1,2 @@ +test: net start +test: net end \ No newline at end of file