Skip to content

Commit

Permalink
add preliminary semaphore implementation
Browse files Browse the repository at this point in the history
Signed-off-by: leongross <[email protected]>
  • Loading branch information
leongross committed Nov 8, 2024
1 parent 7bcf538 commit 1831075
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 36 deletions.
1 change: 1 addition & 0 deletions builder/musl.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ var libMusl = Library{
"env/*.c",
"errno/*.c",
"exit/*.c",
"fcntl/*.c",
"internal/defsysinfo.c",
"internal/libc.c",
"internal/syscall_ret.c",
Expand Down
67 changes: 61 additions & 6 deletions src/runtime/sync.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,72 @@
// 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 (
"sync/atomic"
"unsafe"
)

// 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

type semaRoot struct {
nwait atomic.Uint32
}

var semtable semTable

// Prime to not correlate with any user patterns.
const semTabSize = 251

type semTable [semTabSize]struct {
root semaRoot
pad [64 - unsafe.Sizeof(semaRoot{})]byte // only 64 x86_64, make this variable
}

func (t *semTable) rootFor(addr *uint32) *semaRoot {
return &t[(uintptr(unsafe.Pointer(addr))>>3)%semTabSize].root
}

//go:linkname semacquire internal/poll.runtime_Semacquire
func semacquire(sema *uint32) {
// TODO the "net" pkg calls this, so panic() isn't an option. Right
// now, just ignore the call.
// panic("todo: semacquire")
if cansemacquire(sema) {
return
}
}

// Copied from src/runtime/sema.go
func cansemacquire(addr *uint32) bool {
for {
v := atomic.LoadUint32(addr)
if v == 0 {
return false
}
if atomic.CompareAndSwapUint32(addr, v, v-1) {
return true
}
}
}

//go:linkname semrelease internal/poll.runtime_Semrelease
func semrelease(sema *uint32) {
// TODO the "net" pkg calls this, so panic() isn't an option. Right
// now, just ignore the call.
// panic("todo: semrelease")
root := semtable.rootFor(sema)
atomic.AddUint32(sema, 1)
if root.nwait.Load() == 0 {
return
}
}
51 changes: 21 additions & 30 deletions testdata/net.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package main

import (
"bytes"
"fmt"
"io"
"net"
"strconv"
"time"
)

// Test golang network package integration for tinygo.
// This test is not exhaustive and only tests the basic functionality of the package.

const (
TEST_PORT = 9000
)

var (
testsPassed uint
lnPort int
err error
sendBuf = &bytes.Buffer{}
recvBuf = &bytes.Buffer{}
recvBuf []byte
)

var (
Expand All @@ -28,55 +22,52 @@ var (

func TestDialListen() {
// listen thread
listenReady := make(chan bool, 1)
go func() {
ln, err := net.Listen("tcp", ":"+strconv.FormatInt(TEST_PORT, 10))
ln, err := net.Listen("tcp4", ":0")
if err != nil {
fmt.Printf("error listening: %v\n", err)
println("error listening: ", err)
return
}
lnPort = ln.Addr().(*net.TCPAddr).Port

listenReady <- true
conn, err := ln.Accept()
if err != nil {
fmt.Printf("error accepting: %v\n", err)
println("error accepting:", err)
return
}

recvBuf.Reset()
_, err = conn.Read(recvBuf.Bytes())
if err != nil {
fmt.Printf("error reading: %v\n", err)
recvBuf = make([]byte, len(testDialListenData))
if _, err := io.ReadFull(conn, recvBuf); err != nil {
println("error reading: ", err)
return
}

// TODO: this is racy
if recvBuf.String() != string(testDialListenData) {
fmt.Printf("error: received data does not match sent data: '%s' != '%s'\n", recvBuf.String(), string(testDialListenData))
if string(recvBuf) != string(testDialListenData) {
println("error: received data does not match sent data", string(recvBuf), " != ", string(testDialListenData))
return
}
conn.Close()

return
}()

// hacky way to wait for the listener to start
time.Sleep(1 * time.Second)

sendBuf.Reset()
fmt.Fprint(sendBuf, testDialListenData)
conn, err := net.Dial("tcp4", "127.0.0.1:"+strconv.FormatInt(TEST_PORT, 10))
<-listenReady
conn, err := net.Dial("tcp4", "127.0.0.1:"+strconv.FormatInt(int64(lnPort), 10))
if err != nil {
fmt.Printf("error dialing: %v\n", err)
println("error dialing: ", err)
return
}

if _, err = conn.Write(sendBuf.Bytes()); err != nil {
fmt.Printf("error writing: %v\n", err)
if _, err = conn.Write(testDialListenData); err != nil {
println("error writing: ", err)
return
}
}

func main() {
fmt.Printf("test: net start\n")
println("test: net start")
TestDialListen()
fmt.Printf("test: net end\n")
println("test: net end")
}

0 comments on commit 1831075

Please sign in to comment.