Skip to content

proposal: cmd/compile: go:readonly comment directive for body-less functions #67364

@Jorropo

Description

@Jorropo

Proposal Details

Add go:readonly comment directive for body-less functions indicating the following arguments and any pointers exclusively accessed through a readonly argument wont be modified:

// writeBlocks will update vector (read-hash-write) and read extra and bytes.
//
//go:nescape
//go:readonly extra, bytes
func writeBlocks(vector *[4]uint64, extra *[32]byte, bytes []byte)

The point of this is to hook up with 925d2fb this would allow []byte(str) conversions to be elided when the byte string is passed as a readonly argument to an assembly optimized subroutine.

github.com/cespare/xxhash implements Sum64String(string) uint64 and (*Digest).WriteString(string) using unsafe which allows to skip the allocation either by using (*strings.Reader).WriteTo or io.WriteString.
Other hashing libraries such as crypto do not implement this optimization, this could allow to implement this using compiler optimizations rather than duplicating APIs for []byte and string. (*note: this assume you are using a devirtualized hash.Hash or a concrete hasher implementation, which is now possible and not unrealistic, particularly for h := sha256.New(); h.Write([]byte(str)) patterns)

In the future this could improve register allocation in some rare edge cases so I don't expect this to be implemented.
Other stronger compilers like GCC and LLVM might already implement such logic in their register allocators, then work in gofrontend could be much easier (as I assume this would amount to setting up metadata flags on the function call nodes passed to gcc or llvm) so who knows.


The body-less function is allowed to read from memory, which is arguably a side effect when using -race altho I don't think very many body-less functions implement any kind of race tracking.


For slices the body-less function would assume to not be allowed to write past capacity.
If some memory is accessible through an unknown and a readonly pointer the function is allowed to write to it:

//go:readonly b
func doSomething(a, b []byte)

func f() {
 var x [64]byte
 doSomething(x[:32:32], x[:])
}

Here the compiler would be allowed to assume only the last 32 bytes of the x array are readonly (I expect compilers to not implement this or to assume x maybe completely overwritten).


If a pointer to a pointer is readonly then both memory are readonly:

type s struct{
 v *int
}

//go:readonly a
func doSomething(a *s)

func f() {
 var x int // here the compiler is allowed to assume `x` will never be modified by `doSomething`
 doSomething(&S{&x})
}

Note: this apply transitively to any amount of level.


Combining the two edge cases above, if a pointer to a pointer is accessible both through unknown and readonly paths, the body-less function may modify it:

type s struct{
 v *int
}

//go:readonly b
func doSomething(a, b *s)

func f() {
 var x int // x maybe modified through a
 doSomething(&S{&x}, &S{&x})
}

The body-less function is assumed to maybe write to anything part of the global scope:

var x int

//go:readonly a
func doSomething(a *int)

func f() {
 doSomething(&x) // doSomething is allowed to modify x
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Proposalcompiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Status

    No status

    Status

    Incoming

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions