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

Tidy up serialization layer #154

Closed
wants to merge 85 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
1d10e0d
Don't rely on layout of complex numbers
chriso Jul 3, 2024
7bce0bc
Use reflect.TypeFor to get reflect types
chriso Jul 3, 2024
86b7c22
Move extraction of iface ptr to unsafe
chriso Jul 3, 2024
f657b8f
Avoid casting unsafe.Pointer to *string
chriso Jul 3, 2024
37678d3
Consolidate reflect.TypeFor calls
chriso Jul 3, 2024
8150720
Fix bug when serializing reflect.Map
chriso Jul 3, 2024
1759855
Serialize more types of reflect.Value
chriso Jul 3, 2024
267006a
Fix serialization of reflect.Value containing closures
chriso Jul 3, 2024
7ecc88d
Remove broken support for serializing reflect.Type
chriso Jul 3, 2024
0c0d0b9
Support serializing reflect.Value of a struct with unexported fields
chriso Jul 3, 2024
b03935c
Use a lower level serialization path for arrays
chriso Jul 3, 2024
806152e
Consolidate the serialization paths
chriso Jul 3, 2024
e8fcfaf
Support deserializing reflect.Value containing unsafe.Pointer
chriso Jul 3, 2024
e40c67b
Start to consolidate deser paths
chriso Jul 3, 2024
d0f7eaf
Remove dead code
chriso Jul 3, 2024
8204c13
Lint fixes
chriso Jul 3, 2024
6bdd483
Restore serialization of uintptr
chriso Jul 3, 2024
bf3fbae
Avoid creating extra reflect.Value when deserializing
chriso Jul 3, 2024
98e83fb
Fix reflect.Type support
chriso Jul 3, 2024
5921de9
Support deserializing reflect.Value containing interface{}
chriso Jul 3, 2024
dfa3f48
Avoid UnsafeAddr()
chriso Jul 3, 2024
ca89482
Consolidate deserialization paths
chriso Jul 3, 2024
a5b5b58
Don't deref unsafe.Pointer during serialization
chriso Jul 3, 2024
74557cd
Consolidate handlers for reflect.Type and reflect.Value
chriso Jul 3, 2024
1ef15d7
Use reflect.Value instead of unsafe.Pointer in custom serde layer
chriso Jul 3, 2024
7c9794c
Simplify map serialization
chriso Jul 3, 2024
3c213d7
Simplify custom serde path
chriso Jul 3, 2024
957ca4c
Simplify map serialization path
chriso Jul 3, 2024
fd6c05a
No need to pass str pointer here
chriso Jul 3, 2024
8245001
Avoid ptr arithmetic when serializing arrays
chriso Jul 3, 2024
fec87cb
WIP visitor
chriso Jul 3, 2024
64e2f99
Scan into reflect.Value
chriso Jul 3, 2024
a22742c
Add pointers created through interface indirection
chriso Jul 3, 2024
8061670
Recurse into slices
chriso Jul 3, 2024
c64005f
Fix scanning of closures
chriso Jul 3, 2024
a23442b
Remove serializeAny and deserializeAny
chriso Jul 4, 2024
4396bc3
Use the visitor during serialization
chriso Jul 4, 2024
95bb19c
Inline some helpers
chriso Jul 4, 2024
7a660e5
Fix incorrect detection of direct interface types
chriso Jul 4, 2024
b76a016
Inline and simplify func/closure serialization
chriso Jul 4, 2024
3199593
Serialize but overwrite the fn addr
chriso Jul 4, 2024
cecaf21
Use the visitor to scan closure fields
chriso Jul 4, 2024
59fd8e1
Move helpers related to functab and buildid to a new reflectext package
chriso Jul 4, 2024
060572d
Move reflect.Type related helpers to reflectext
chriso Jul 4, 2024
8f60f73
Migrate some unsafe helpers
chriso Jul 4, 2024
05f1755
Extract a helper to set slice data/len/cap
chriso Jul 4, 2024
37593fa
Move more unsafe helpers
chriso Jul 4, 2024
8e3dc4a
Move the Visitor interface and helpers
chriso Jul 4, 2024
856216b
Tidy up reconstruction of functions/closures
chriso Jul 4, 2024
073f46d
Make sig more readable
chriso Jul 4, 2024
4b4bdf4
Tidy up iface helper
chriso Jul 4, 2024
f89556a
Address FIXMEs
chriso Jul 4, 2024
1d1cdd7
Fill in missing docs and add TODOs
chriso Jul 4, 2024
52b98a7
Keep the unsafe stuff together
chriso Jul 4, 2024
48a5274
Reuse unsafe types
chriso Jul 4, 2024
e7e2b07
Always pass the full reflect.Value
chriso Jul 4, 2024
c9a7577
Settle on the wrapper pattern
chriso Jul 4, 2024
f9d15fc
Choose a better name for the region helpers
chriso Jul 4, 2024
1e40179
Keep function header as an internal detail
chriso Jul 4, 2024
4a47095
Settle on a pattern for the unsafe helpers
chriso Jul 4, 2024
a43f5f8
Thread a context through
chriso Jul 4, 2024
4b5d9fe
Start to split out encoding related functions
chriso Jul 4, 2024
27badae
Fix flag doc
chriso Jul 4, 2024
0ccce59
Move more lower level encoders
chriso Jul 4, 2024
e22ae3f
Consolidate helpers
chriso Jul 4, 2024
960a05c
Continue to simplify helpers
chriso Jul 4, 2024
b5d0eb4
No need to pass around pointers to reflect.Value when CanAddr()
chriso Jul 5, 2024
fb5f1ca
No need to pass a pointer here either
chriso Jul 5, 2024
34eb2c4
Start migrating the deserialization path to the Visitor
chriso Jul 5, 2024
bf6c6c0
Migrate to the visitor
chriso Jul 5, 2024
dadaed8
Remove outdated FIXME
chriso Jul 5, 2024
7ffad5d
Improve internal docs
chriso Jul 5, 2024
ec0fdaf
Let the visitor handle structs in the deser path
chriso Jul 5, 2024
284e330
Improve internal docs
chriso Jul 5, 2024
b1d48b6
Move unsafe array index access to a helper
chriso Jul 5, 2024
20d7a74
Simplify deserializer
chriso Jul 5, 2024
98b6ab4
Simplify the scanner so that it accepts a reflect.Value
chriso Jul 5, 2024
7bb67ac
Use the current visitor to populate closure vars
chriso Jul 5, 2024
6d92641
More rationale for SetSlice
chriso Jul 6, 2024
53e0e0a
Explain and test interned values
chriso Jul 6, 2024
3fcda79
Consolidate unsafe helpers
chriso Jul 6, 2024
224c0bc
Tidy up helpers again
chriso Jul 6, 2024
7466a43
Minor simplifications
chriso Jul 6, 2024
b24f1d4
Link contexts together
chriso Jul 6, 2024
2de08bd
Provide the current visitor location as a path string
chriso Jul 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions compiler/coroutine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/dispatchrun/coroutine"
. "github.com/dispatchrun/coroutine/compiler/testdata"
"github.com/dispatchrun/coroutine/internal/reflectext"
"github.com/dispatchrun/coroutine/types"
)

Expand Down Expand Up @@ -313,12 +314,12 @@ func TestCoroutineYield(t *testing.T) {
// package.
for _, test := range tests {
if test.coro != nil {
addr := types.FuncAddr(test.coro)
fn := types.FuncByAddr(addr)
addr := reflectext.FuncAddr(test.coro)
fn := reflectext.FuncByAddr(addr)
types.RegisterFunc[func()](fn.Name)
} else {
addr := types.FuncAddr(test.coroR)
fn := types.FuncByAddr(addr)
addr := reflectext.FuncAddr(test.coroR)
fn := reflectext.FuncByAddr(addr)
types.RegisterFunc[func() int](fn.Name)
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/testdata/coroutine_durable.go
Original file line number Diff line number Diff line change
Expand Up @@ -3519,7 +3519,7 @@ func IdentityGenericInt(n int) { IdentityGeneric[int](n) }

//go:noinline
func IdentityGenericClosure[T any](_fn0 T) {
_c := coroutine.LoadContext[int, any]()
_c := coroutine.LoadContext[T, any]()
var _f0 *struct {
IP int
X0 T
Expand Down
8 changes: 8 additions & 0 deletions internal/reflectext/buildid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package reflectext

var buildID string

// BuildID is the build identifier for the binary.
func BuildID() string {
return buildID
}
160 changes: 160 additions & 0 deletions internal/reflectext/func.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package reflectext

import (
"debug/gosym"
"errors"
"io"
"reflect"
"runtime"
"unsafe"
)

// Func represents a function in the program.
type Func struct {
// The address where the function exists in the program memory.
Addr uintptr

// The name that uniquely represents the function.
//
// For regular functions, this values has the form <package>.<function>.
//
// For closures, this value has the form <package>.<function>.func<N>, where
// N starts at 1 and increments for each closure defined in the function.
Name string

// A type representing the signature of the function value.
//
// This field is nil if the type is unknown; by default the field is nil and
// the program is expected to initialize it to a non-nil value for functions
// that may be serialized.
//
// If non-nil, the type must be of kind reflect.Func.
Type reflect.Type

// A struct type representing the memory layout of the closure.
//
// This field is nil if the type is unknown; by default the field is nil and
// the program is expected to initialize it to a non-nil value for closures
// that may be serialized. For regular functions, this field can remain nil
// since regular functions do not capture any values.
//
// If non-nil, the first field of the struct type must be a uintptr intended
// to hold the address to the function value.
Closure reflect.Type
}

// RegisterFunc is a helper function used to register function types. The type
// parameter must be a function type, but no compile nor runtime checks are used
// to enforce it; passing anything other than a function type will likely result
// in panics later on when the program attempts to serialize the function value.
//
// The name argument is a unique identifier of the Go symbol that represents the
// function, which has the package path as prefix, and the dot-separated sequence
// identifying the function in the package.
func RegisterFunc[Type any](name string) {
if f := FuncByName(name); f != nil {
f.Type = reflect.TypeFor[Type]()
}
}

// RegisterClosure is like RegisterFunc but the caller can specify the closure
// type (see types.Func for details).
func RegisterClosure[Type, Closure any](name string) {
if f := FuncByName(name); f != nil {
f.Type, f.Closure = reflect.TypeFor[Type](), reflect.TypeFor[Closure]()
}
}

// FuncAddr returns the address in memory of the closure passed as argument.
//
// This function can only resolve addresses of closures in the compilation unit
// that it is part of; for example, if compiled in a Go plugin, it can only
// resolve the address of functions within that plugin, and the main program
// cannot resolve addresses of functions in the plugins it loaded.
//
// If the argument is a nil function value, the return address is zero.
//
// The function panics if called with a value which is not a function.
func FuncAddr(fn any) uintptr {
if reflect.TypeOf(fn).Kind() != reflect.Func {
panic("value must be a function")
}
header := (*functionHeader)((*interfaceHeader)(unsafe.Pointer(&fn)).ptr)
if header == nil {
return 0
}
return uintptr(header.addr)
}

// FuncByName returns the function associated with the given name.
//
// Addresses in the returned Func value hold the value of the symbol location in
// the program memory.
//
// If the name passed as argument does not represent any function, the function
// returns nil.
func FuncByName(name string) *Func { return functionsByName[name] }

// FuncByAddr returns the function associated with the given address.
//
// Addresses in the returned Func value hold the value of the symbol location in
// the program memory.
//
// If the address passed as argument is not the address of a function in the
// program, the function returns nil.
func FuncByAddr(addr uintptr) *Func { return functionsByAddr[addr] }

var (
functionsByName map[string]*Func
functionsByAddr map[uintptr]*Func
)

func initFunctionTables(pclntab, symtab []byte) {
table, err := gosym.NewTable(symtab, gosym.NewLineTable(pclntab, 0))
if err != nil {
panic("cannot read symtab: " + err.Error())
}

sentinelName, sentinelAddr := sentinel()

tableFunc := table.LookupFunc(sentinelName)
offset := uint64(sentinelAddr) - tableFunc.Entry

functions := make([]Func, len(table.Funcs))
for i, fn := range table.Funcs {
functions[i] = Func{
Addr: uintptr(fn.Entry + offset),
Name: fn.Name,
}
}

functionsByName = make(map[string]*Func, len(functions))
functionsByAddr = make(map[uintptr]*Func, len(functions))

for i := range functions {
f := &functions[i]
functionsByName[f.Name] = f
functionsByAddr[f.Addr] = f
}
}

func readSection(r io.ReaderAt, size uint64) ([]byte, error) {
if r == nil {
return nil, errors.New("section missing")
}
b := make([]byte, size)
n, err := r.ReadAt(b, 0)
if err != nil && n < len(b) {
return nil, err
}
return b, nil
}

//go:noinline
func sentinel() (name string, addr uintptr) {
pc := [1]uintptr{}
runtime.Callers(0, pc[:])

fn := runtime.FuncForPC(pc[0])
return fn.Name(), fn.Entry()
}
21 changes: 10 additions & 11 deletions types/func_test.go → internal/reflectext/func_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package reflectext

import (
"reflect"
Expand Down Expand Up @@ -31,7 +31,7 @@ func TestFunctionAddress(t *testing.T) {
}

func TestFunctionLookup(t *testing.T) {
name := "github.com/dispatchrun/coroutine/types.TestFunctionLookup"
name := "github.com/dispatchrun/coroutine/internal/reflectext.TestFunctionLookup"
sym1 := FuncByName(name)
sym2 := FuncByAddr(FuncAddr(TestFunctionLookup))

Expand All @@ -42,11 +42,10 @@ func TestFunctionLookup(t *testing.T) {

func TestClosureAddress(t *testing.T) {
f := op(42, 1)
p := *(*unsafe.Pointer)(unsafe.Pointer(&f))
c := (*closure)(p)
header := *(**functionHeader)(unsafe.Pointer(&f))

name := "github.com/dispatchrun/coroutine/types.op.func1"
addr1 := c.addr
name := "github.com/dispatchrun/coroutine/internal/reflectext.op.func1"
addr1 := uintptr(header.addr)
addr2 := FuncAddr(f)

if addr1 != addr2 {
Expand All @@ -57,7 +56,7 @@ func TestClosureAddress(t *testing.T) {
func TestClosureLookup(t *testing.T) {
f := op(1, 2)

name := "github.com/dispatchrun/coroutine/types.op.func1"
name := "github.com/dispatchrun/coroutine/internal/reflectext.op.func1"
sym1 := FuncByName(name)
sym2 := FuncByAddr(FuncAddr(f))

Expand All @@ -67,7 +66,7 @@ func TestClosureLookup(t *testing.T) {
}

func TestRehydrateFunction(t *testing.T) {
f := FuncByName("github.com/dispatchrun/coroutine/types.op")
f := FuncByName("github.com/dispatchrun/coroutine/internal/reflectext.op")
v := reflect.New(f.Type)
p := v.UnsafePointer()

Expand All @@ -88,7 +87,7 @@ func TestRehydrateFunction(t *testing.T) {
}

func TestRehydrateClosure(t *testing.T) {
f := FuncByName("github.com/dispatchrun/coroutine/types.op.func1")
f := FuncByName("github.com/dispatchrun/coroutine/internal/reflectext.op.func1")
v := reflect.New(f.Closure)
p := v.UnsafePointer()

Expand Down Expand Up @@ -125,10 +124,10 @@ type opFunc1Closure struct {
// This init function contains the work that would normally be done by the
// compiler to generate the reflect data necessary to serialize functions.
func init() {
op := FuncByName("github.com/dispatchrun/coroutine/types.op")
op := FuncByName("github.com/dispatchrun/coroutine/internal/reflectext.op")
op.Type = reflect.TypeOf(func(int, int) (_ func() int) { return })

fn := FuncByName("github.com/dispatchrun/coroutine/types.op.func1")
fn := FuncByName("github.com/dispatchrun/coroutine/internal/reflectext.op.func1")
fn.Type = reflect.TypeOf(func() (_ int) { return })
fn.Closure = reflect.TypeOf(opFunc1Closure{})
}
2 changes: 1 addition & 1 deletion types/obj_darwin.go → internal/reflectext/obj_darwin.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package reflectext

import (
"bytes"
Expand Down
2 changes: 1 addition & 1 deletion types/obj_linux.go → internal/reflectext/obj_linux.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package reflectext

import (
"bytes"
Expand Down
39 changes: 39 additions & 0 deletions internal/reflectext/type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package reflectext

import (
"reflect"
"unsafe"
)

var (
AnyType = reflect.TypeFor[any]()

BoolType = reflect.TypeFor[bool]()

IntType = reflect.TypeFor[int]()
Int8Type = reflect.TypeFor[int8]()
Int16Type = reflect.TypeFor[int16]()
Int32Type = reflect.TypeFor[int32]()
Int64Type = reflect.TypeFor[int64]()

UintType = reflect.TypeFor[uint]()
Uint8Type = reflect.TypeFor[uint8]()
Uint16Type = reflect.TypeFor[uint16]()
Uint32Type = reflect.TypeFor[uint32]()
Uint64Type = reflect.TypeFor[uint64]()

Float32Type = reflect.TypeFor[float32]()
Float64Type = reflect.TypeFor[float64]()

Complex64Type = reflect.TypeFor[complex64]()
Complex128Type = reflect.TypeFor[complex128]()

ByteType = reflect.TypeFor[byte]()
StringType = reflect.TypeFor[string]()

UintptrType = reflect.TypeFor[uintptr]()
UnsafePointerType = reflect.TypeFor[unsafe.Pointer]()

ReflectValueType = reflect.TypeFor[reflect.Value]()
ReflectTypeType = reflect.TypeFor[reflect.Type]()
)
Loading
Loading