Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: enable native golang linux networking #4498

Open
wants to merge 14 commits into
base: dev
Choose a base branch
from
40 changes: 34 additions & 6 deletions loader/goroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "/", "\\")
}
Expand Down
47 changes: 47 additions & 0 deletions src/runtime/netpoll.go
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 38 additions & 0 deletions src/runtime/netpoll_generic.go
Original file line number Diff line number Diff line change
@@ -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)
}
15 changes: 8 additions & 7 deletions src/runtime/poll.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Copy link
Member

@aykevl aykevl Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you put in debugging code, please remove it before sending in a PR. It makes the diff bigger and more work to review.
Generally, take a look at the PR diff and see if there's any part of it that is not necessary and can be removed (such as the changes to .gitmodules and the signal changes that are still part of this PR).
(Also, how do you know that you can just remove this panic?)

}

//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")
}
43 changes: 41 additions & 2 deletions src/runtime/sync.go
Original file line number Diff line number Diff line change
@@ -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."
leongross marked this conversation as resolved.
Show resolved Hide resolved
//
// 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()
}
17 changes: 17 additions & 0 deletions src/syscall/forklock.go
Original file line number Diff line number Diff line change
@@ -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)
}
11 changes: 10 additions & 1 deletion src/syscall/syscall_libc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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
73 changes: 73 additions & 0 deletions testdata/net.go
Original file line number Diff line number Diff line change
@@ -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")
}
2 changes: 2 additions & 0 deletions testdata/net.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test: net start
test: net end
Loading