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

proposal: cmd/compile: make 64-bit fields be 64-bit aligned on 32-bit systems, add //go:packed directive on structs #36606

Open
danscales opened this issue Jan 16, 2020 · 30 comments
Labels
Projects
Milestone

Comments

@danscales
Copy link

@danscales danscales commented Jan 16, 2020

Summary: We propose to change the default layout of structs on 32-bit systems such that 64-bit fields will be 64-bit aligned. The layout of structs on 64-bit systems will not change. For compatibility reasons (and finer control of struct layout), we also propose the addition of a //go:packed directive that applies to struct types. When the //go:packed directive is specified immediately before a struct type, then that struct will have a fully-packed layout, where fields are placed in order with no padding between them and hence no alignment based on types. The developer must explicitly add padding to enforce any desired alignment.

The main goal of this change is to avoid the bugs that frequently happen on 32-bit systems, where a developer wants to be able to do 64-bit operations on a 64-bit field (such as an atomic operation), but gets an alignment error because the field is not 64-bit aligned. With the current struct layout rules, a developer must often add explicit padding in order to make sure that such a 64-bit field is on a 64-bit boundary. As shown by repeated mentions in issue #599 (18 in 2019 alone), developers still often run into this problem. They may only run into it late in the development cycle as they are testing on 32-bit architectures, or when they execute an uncommon code path that requires the alignment.

As an example, the struct for ticks in runtime/runtime.go is declared as

var ticks struct {
	lock mutex
	pad  uint32 // ensure 8-byte alignment of val on 386
	val  uint64
}

so that the val field is properly aligned on 32-bit architectures. With the change in this proposal, it could be declared as:

var ticks struct {
	lock mutex
	val  uint64
}

and the val field would always be 64-bit aligned, even if other fields are added to struct.

We do not propose changing the alignment of 64-bit types which are arguments or return values, since that would change the Go calling ABI on 32-bit architectures. For consistency, we also don't propose to change the alignment of 64-bit types which are local variables. However, since we are changing the layout of some structs, we could be changing the ABI for functions that take a struct as an argument or return value. However, if assembly code is accessing struct fields, it should be using the symbolic constants (giving the offset of each field in a struct) that are available in go_asm.h (which is automatically generated for any package which contains an assembly file).

However, we do require a new directive for compatibility in the case of cgo and also interactions with the operating system. We propose the addition of a //go:packed directive that applies to struct types. When the //go:packed directive is specified immediately before a struct type, then that struct will have a fully-packed layout, where fields are placed in order with no padding between them and hence no alignment based on types. The developer must explicitly add padding fields to enforce any desired alignment within the structure.

We would then use the //go:packed directive for cgo-generated struct types (with explicit padding added as needed). Also, given the strict layout requirements for structures passed to system calls, we would use //go:packed for structs that are used for system calls (notably Flock_t and Stat_t, used by Linux system calls). We can enforce the use of //go:packed for structures that are passed to system calls (typically via pointers) with the use of a go vet check. I don't think we would particularly encourage the use of //go:packed in any other situations (as with most directives), but it might be useful in a few specific other cases.

This proposal is essentially a solution to issue #599. A related issue #19057 proposes a way to force the overall alignment of structures. That proposal does not propose changing the default alignment of 64-bit fields on 32-bit architectures (the source of the problems mentioned in issue #599), but would provide a mechanism to explicitly align 64-bit fields without using developer-computed padding. With that proposal, aligning a uint64 field (for example) in a struct to a 64-bit boundary on a 32-bit system would require replacing the uint64 type with a struct type that has the required 64-bit alignment (as I understand it).

Compatibility: We are not changing the alignment of arguments, return variables, or local variables. Since we would be changing the default layout of structs, we could affect some programs running on 32-bit systems that depend on the layout of structs. However, the layout of structs is not explicitly defined in the Go language spec, except for than minimum alignment, and we are maintaining the previous minimum alignments. So, I don't believe this change breaks the Go 1 compatibility promise. If assembly code is accessing struct fields, it should be using the symbolic constants (giving the offset of each field in a struct) that are available in go_asm.h. go_asm.h is automatically generated and available for each package that contains an assembler file, or can be explicitly generated for use elsewhere via go tool compile -asmhdr go_asm.h ....

@gopherbot gopherbot added this to the Proposal milestone Jan 16, 2020
@gopherbot gopherbot added the Proposal label Jan 16, 2020
@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jan 16, 2020

If we are going to consider adding a go:packed magic comment that affects code generation, then I think we should also consider a magic comment that permits specifying the desired alignment of a type. That will permit people to align types to a wider boundary than would otherwise be used, which permits using vectorization writting in assembler or C. See #19057.

@josharian

This comment has been minimized.

Copy link
Contributor

@josharian josharian commented Jan 17, 2020

We expect that any assembler functions that depend on the layout of a struct argument or return value should be using unsafe.Offsetof to determine field offsets within a struct.

Does the assembler accept unsafe.Offsetof for FP offsets? I don’t recall ever seeing that in assembly code.

The good news is that vet checks these offsets, so breakage should be caught as a matter of course. (It’ll mean teaching go/types about the annotations, but that would need to happen anyway.)

Or do I misunderstand?

@josharian

This comment has been minimized.

Copy link
Contributor

@josharian josharian commented Jan 17, 2020

During the discussion of changing the Go ABI, several people expressed the opinion that if we're going to break a bunch of assembly code, we'd rather do that only once. I share that opinion.

Making this change and then an ABI change would mean breaking Go assembly twice.

I'd hate to gate this proposal on figuring out the ABI changes. What if instead, as a prerequisite for this change, we teach the Go assembler some way to express things like "first argument" or "offset of field F within struct S". Then we could update the assembly once to use these higher level constructs, and if we change the ABI later, if we've done our job well, no further assembly changes would be necessary. It'd also lower the cost of ABI experimentation.

@danscales

This comment has been minimized.

Copy link
Author

@danscales danscales commented Jan 17, 2020

We expect that any assembler functions that depend on the layout of a struct argument or return value should be using unsafe.Offsetof to determine field offsets within a struct.

Does the assembler accept unsafe.Offsetof for FP offsets? I don’t recall ever seeing that in assembly code.

I was thinking that you could have a Go init routine that stores the relevant values of unsafe.OffsetOf in some global variables that are accessed by the assembler routine. Also, Ian pointed out to me that in many situations you can use 'go tool compile -asmhdr go_asm.h ...' on the Go file that defines the type to get the relevant offsets written out to a go_asm.h file. I can clarify this in the proposal.

The good news is that vet checks these offsets, so breakage should be caught as a matter of course. (It’ll mean teaching go/types about the annotations, but that would need to happen anyway.)

Or do I misunderstand?

I don't really understand what you mean by 'vet checks these offsets' in this case. What vet rule is this? Or are you talking about the suggested new vet checks that I mention in the proposal?

Making this change and then an ABI change would mean breaking Go assembly twice.

As I understand it, the goal with introducing a new ABI would be to use it in Go code mainly, and not break existing assembly code (which would stay with the original Go ABI, referenced as ABI0 in
Austin's changes).

I do agree that this proposal could break some existing assembly code. I'm guessing that it might be quite minimal (but that may definitely be wrong), and certainly possibly breaking some assembler code is relevant to deciding about the benefits/drawbacks of this proposal.

@josharian

This comment has been minimized.

Copy link
Contributor

@josharian josharian commented Jan 17, 2020

I can clarify this in the proposal.

I think that that would be helpful.

I don't really understand what you mean by 'vet checks these offsets' in this case.

I was thinking of https://github.com/golang/tools/blob/master/go/analysis/passes/asmdecl/asmdecl.go

@beoran

This comment has been minimized.

Copy link

@beoran beoran commented Jan 20, 2020

I hope that whatever change there is to the ABI or to struct layouts, that it does not make interfacing with the OS or with C code any harder than it is now. At best the ABI and struct layout stays as it is and any special directives only affect that when used, not by default.

The only reasons I consider worth while of to change the Go ABI would be to make C calls faster by getting rid of the segmented stack, for the rest I'm not sure what the benefits would be.

@rsc rsc added this to Incoming in Proposals Jan 22, 2020
@bradfitz

This comment has been minimized.

Copy link
Contributor

@bradfitz bradfitz commented Jan 22, 2020

The other day I proposed adding a zero-width type (like unsafe.Packed) that could be embedded instead of a magic comment. (We do something similar with sync/cond.go:type noCopy struct{}.)

@rsc

This comment has been minimized.

Copy link
Contributor

@rsc rsc commented Jan 22, 2020

Note that it is critical that this proposal include a vet check to ensure that all places where we hand data structures to C/syscall/asm/DLL get marked as "packed". That gives us the benefit, after going through the work of marking all those places (helped by vet), of letting the compiler rearrange fields in unmarked structs, such as to fill in alignment-induced gaps with upcoming fields. (We've talked about doing this for a long time, but we held back because we didn't want to break compatibility with C/etc. If we're not compatible anyway, then we should make sure to get a benefit. Today, we rearrange fields to fill in gaps today by hand, which is not great.)

The exact annotation for how to mark "this struct is packed" is less important than the semantic change. We should probably decide "do we want to do this at all" first.

The alignment note that Ian suggested is another semantic change. That suggestion is to mark a specific type as having a specific alignment (anywhere a value of that type is represented in memory). That may or may not be separable from this issue, but if we think we want to do it at some point, it would be good to choose a marking scheme for packed/not that matches an eventual marking scheme for per-type overall alignment.

@rsc rsc moved this from Incoming to Active in Proposals Jan 22, 2020
@beoran

This comment has been minimized.

Copy link

@beoran beoran commented Jan 23, 2020

I think that perhaps, it's already too late to make this change, as there are hundreds if not thousands of Go projects out there that use C/syscall/asm/DLL with the assumption that the struct layout in Go is as it is today, without automatic rearrangements. While what we have now isn't all too great, it does work in practice, and I know that if I am careful, then I can manually lay out the Go struct in such a way that the kernel/C code/DLL can use it.

As an aside, while it is true that in this kind of low level situations, instead of a struct I could use a []byte in conjunction with "encoding/binary", this is not a very satisfactory solution due to the overhead of the function calls and the lack of convenience.

While letting the compiler automatically rearrange fields in a struct will save on some memory space, I don't see that as a benefit that is worth breaking millions of lines of working code over. Making such a change now truly will lead to an incompatible Go v2, which risks the same problem as python 3 had.

Rather than making such changes, I would specify and document how one can manually lay out a struct efficiently, and then make a tool that can rearrange structs to be more compact according to this documentation. Furthermore, I would be in favor of changes which make laying out a struct manually more easy or more precise.

Furthermore, a "go vet" check alone is not enough to fix this, we would also need a "go fix" for this so this can be fixed automatically. However, I suspect that either will be difficult to implement because exported structs can be used freely everywhere, so it is hard to see if a struct is involved in a situation where the compiler should not rearrange it.

I do agree that this issue is not separate from other alignment and packing issues. If changes are to be made, they should all be made together, to make the transition somewhat easier.

@aclements

This comment has been minimized.

Copy link
Member

@aclements aclements commented Jan 24, 2020

During the discussion of changing the Go ABI, several people expressed the opinion that if we're going to break a bunch of assembly code, we'd rather do that only once. I share that opinion.

Dan's change doesn't affect argument layouts, so no FP offsets would change in assembly code. The prototype implementation actually separates the concerns of struct layout from argument frame layout in order to keep this working. Since we have the ABI0/ABIInternal split, we could in principle use struct-style layout for Go calls while still keeping the existing alignment rules for assembly calls (though I don't know if that would be worthwhile).

The one place that it does potentially affect assembly code is if the assembly hard-codes offsets into Go structures. Our hope is that any assembly that is using struct offsets is just including go_asm.h and using the symbolic values already created by the compiler. (You don't even have to use "go tool compile -asmhdr go_asm.h" like Dan mentioned above; the go tool does this automatically.)

@aclements

This comment has been minimized.

Copy link
Member

@aclements aclements commented Jan 24, 2020

A few points that have been made in in-person discussions about this proposal:

  • Some architectures will trap on unaligned access. We can either say that's up to the user to avoid, or have the compiler emit the necessary code to avoid the unaligned access. GCC, for example, will load unaligned fields one byte at a time if it has to.

  • @dr2chase pointed out one reason you may need both a "packed" annotation and an "alignment" annotation on the same struct. If "packed" has the effect of making all fields alignment 1, then the whole struct has alignment 1 and you have no control over what happens if that struct is embedded in another type. In particular, you may be laying out a type carefully so that some fields are aligned, but that alignment may be broken when the type is put in a larger type. However, this can be handled if you can specify both that the struct is packed and explicitly specify its overall alignment.

  • @mknyszek pointed out that the garbage collector requires that pointers be word-aligned. It would be unfortunate to say "you can't have pointers in a packed struct", so I think pointers need to be word-aligned even in packed structs (which also affects overall struct alignment). We can either make this happen silently, or we can say it's an error if a pointer in a packed struct doesn't already fall on a word boundary.

@aclements

This comment has been minimized.

Copy link
Member

@aclements aclements commented Jan 24, 2020

I think that perhaps, it's already too late to make this change, as there are hundreds if not thousands of Go projects out there that use C/syscall/asm/DLL with the assumption that the struct layout in Go is as it is today, without automatic rearrangements.

I personally think this is a significant overestimate, but of course, I don't have any data to back up that claim either. If we implement a vet check, we could at least automatically scan a large corpus and get a better sense.

While letting the compiler automatically rearrange fields in a struct will save on some memory space, I don't see that as a benefit that is worth breaking millions of lines of working code over. ... Rather than making such changes, I would specify and document how one can manually lay out a struct efficiently ...

Note that we're not currently proposing field rearrangement. This proposal isn't about saving memory at all; it's about addressing a persistent source of run-time crashes in configurations that are often poorly tested.

Furthermore, a "go vet" check alone is not enough to fix this, we would also need a "go fix" for this so this can be fixed automatically.

Yes, we've actually been talking about this. It probably wouldn't be a "go fix" just because "go fix" is built to make as few assumptions about the code as possible, but it could be a new flag to "go vet", since "go vet" can do the sorts of type and flow analysis that you want for this. We've been thinking about doing this for some other vet checks as well that have clear automatic fixes.

@rsc

This comment has been minimized.

Copy link
Contributor

@rsc rsc commented Jan 24, 2020

@beoran

I think that perhaps, it's already too late to make this change, as there are hundreds if not thousands of Go projects out there that use C/syscall/asm/DLL with the assumption that the struct layout in Go is as it is today, without automatic rearrangements. While what we have now isn't all too great, it does work in practice, and I know that if I am careful, then I can manually lay out the Go struct in such a way that the kernel/C code/DLL can use it.

Two things.

  1. It does not actually work in practice all the time. Most of the time, yes. But not all the time. Cgo silently skips over fields that are aligned in ways it cannot express to Go, which is a persistent low-rate source of problems. Same for the lack of 64-bit alignment when using 64-bit atomics on 32-bit systems. The point of the annotation is to allow fixing the latter, and it also fixes the former.

  2. All the existing code can be grandfathered in. The go.mod file lets us identify which version of Go code expects, and if we do any rearrangement, we would almost certainly disable it for code written against older versions of Go.

@beoran

This comment has been minimized.

Copy link

@beoran beoran commented Jan 24, 2020

Ok, if I limit myself to just this proposal, as opposed to other related ABI changes, then I have to admit that a //go:packed annotation, or similar functionality by itself, is useful to make it easier to interact with C/DLL/kernel. I didn't know about the limitation of cgo, so if this annotation could fix that, then that would be great.

I'm more worried about the other part of this proposal to change the alignment, as this breaks the ABI of go. If this change is disabled for old code though the go statement in the go.mod file, then that would be sufficient to keep old code working. Of course the "old" ABI must then be maintained "forever", or at least, say 10 years, and must be interoperable with the new ABI.

And I am glad to hear that automatic fixing using go vet is being considered.

With the safeguards of go module versioning and go vet in place, and if properly planned, it might be possible to make ABI changes to go without breaking existing code. Still, I'd prefer that, like generics, the issue of changing the go ABI be well thought out, without precipitation, so we can get it right, and hopefully only have to do it once every 10 years or less.

@aclements

This comment has been minimized.

Copy link
Member

@aclements aclements commented Jan 25, 2020

@mknyszek pointed out to me that I completely misunderstood one of the concerns with the ABI: while Dan's proposing to maintain the current behavior for aligning the starts of arguments (which means argument frame and struct layout may differ, unlike right now), if you actually pass a struct by value to an assembly function, then it may be affected by an alignment change in that struct.

Does this actually happen in the wild? I'm not sure. It would require 32-bit assembly that takes a struct argument whose layout actually changes. vet would be able to detect it even without a new analysis, so we could try running it on a large corpus to get a sense.

@mknyszek proposed that we could create an ABI wrapper to relayout such structs in the arguments/results to assembly functions. This is kind of a pain, but is certainly doable. If we go down this path, maybe we also make ABIInternal Go functions continue to take a struct-style argument frame.

Still, I'd prefer that, like generics, the issue of changing the go ABI be well thought out

We have been thinking very carefully about how to change the ABI (it's not as flashy as generics :). We already have the system in place (modulo a few rough edges) to maintain strict calling convention compatibility with existing assembly code, even if we change the calling convention in Go code. This is what I was referring to with an "ABI wrapper" above.

@beoran

This comment has been minimized.

Copy link

@beoran beoran commented Jan 25, 2020

@aclements Great, I fully agree with the idea at keeping the current ABI as ABI0. Good to see that there is a plan. However, I am missing something in there. It only mentioned assembly.

However, there is also Cgo, DLL calls and direct OS calls to keep in mind, like I do with my Linux input library https://gitlab.com/beoran/galago/blob/master/os/linux/input_linux.go. I would like thought to be given also to these use cases and preferably mention them also in that document.

Edit: I reread the document and cgo is mentioned, but I feel more though should be given to it and the use cases I mentioned above.

@danscales

This comment has been minimized.

Copy link
Author

@danscales danscales commented Jan 26, 2020

@beoran: just to get back to the immediate proposal (which only changes the alignment of 64-bit fields on 32-bit systems), it would definitely be helpful if you can send pointers to any of your code (or any other code that you know of) that will be immediately impacted. That is, if you have assembly language code that hard-codes offsets of fields in structs. Or if you have structs (other than cgo, which we plan to handle automatically) that must match existing syscall/dll/C structures, and will no longer match because of the alignment change. Similar to the Flock_t and Stat_t structs we mentioned above, any other such structures would potentially need to use //go:packed and explicit padding to keep the exact alignment desired for syscalls/dlls or other C API (but that is not using cgo).

@danscales

This comment has been minimized.

Copy link
Author

@danscales danscales commented Jan 26, 2020

@mknyszek proposed that we could create an ABI wrapper to relayout such structs in the arguments/results to assembly functions. This is kind of a pain, but is certainly doable. If we go down this path, maybe we also make ABIInternal Go functions continue to take a struct-style argument frame.

@aclements: I'm not sure if I'm understanding, but are you saying we could convert an incoming struct from the new layout back to the Go 1.13 layout via the wrapper, so that the existing assembly doesn't notice the field offset change? I suppose that is possible, but remember that the assembly routine might be taking a pointer to the struct, or access the struct via a chain of multiple pointers. So, it will certainly never be possible to ensure that an assembly routine that hard-codes field offsets will remain compatible with the struct alignment changes.

@beoran

This comment has been minimized.

Copy link

@beoran beoran commented Jan 27, 2020

@danscales As I mentioned before, the code of my "galago" Linux input system does not use CGo, but direct Linux kernel calls to read joysticks, etc. The code is currently written such that, like GCC does for the kernel, 64 bits fields are aligned on 32 bits boundaries on 32 bits systems. Go structs currenly seems to agree mostly with C structs as GCC produces them, which is convenient.

So I think my code is going to be affected, though I will have to test it first. See here for the code "galago" packages:

https://gitlab.com/beoran/galago/tree/master/os/linux

@danscales

This comment has been minimized.

Copy link
Author

@danscales danscales commented Jan 28, 2020

@beoran In your galago code, I see very little use of uint64/int64/float64, and even less for 32-bit systems. In that case, the only 64-bit type in use that I see is Time_t, which is int64 even for 32-bit system. 'Long' however looks to be int32 on a 32-bit system. So, there could possibly be a change in alignment only because of the use of Time_t, but the only use of Time_t is in Timeval, where it is the first field. So, the offset of the fields in this struct would not change on 32-bit systems, though the overall required alignment of the structure would increase from 32-bit to 64-bit alignment. I don't think this should affect any interaction with the OS, but let me know if you foresee some issue.

We will keep your case in mind, and please do send any other pointers to code that might require code changes with this proposal.

@beoran

This comment has been minimized.

Copy link

@beoran beoran commented Jan 28, 2020

@danscales Yes, I can see that in my specific case of direct Linux kernel calls there may not be a problem because the struct layout is such that it should be OK. Perhaps the Linux developers did put the 64 bits members of the struct first on purpose, but I'm not sure that this is true for all direct Linux system calls. There are probably thousands of structs involved in hundreds of system calls, and I'm unsure if all are so well-behaved, so that is certainly something we need to keep in mind.

@hajimehoshi 's Ebiten is also likely to be affected on Windows, because Ebiten's code does make direct DLL calls without using cgo using syscall, to avoid the performance overhead of CGO. Some of the structs in that code are filled with 32 bits integers. For example: https://github.com/hajimehoshi/ebiten/blob/master/internal/glfw/glfw_windows.go.

@dr2chase

This comment has been minimized.

Copy link
Contributor

@dr2chase dr2chase commented Jan 28, 2020

Not sure yet another "great idea" is welcome, but perhaps we can specify field ordering and type alignment separately. Before I go too far with this, I ran into a problem, this might be a common problem, so I'll lead with that.


IF type alignment is specified separately, there will be a problem with conversions from pointer-to-less-aligned-thing to pointer-to-more-aligned-thing. Pointer conversion currently is unchecked -- if the compiler allows it, it happens. If we allow such pointers (and we might want them for atomics, if we like static checking) then we need an answer for this. I think the least-surprise option would be to enhance the semantics of a plain pointer cast to include an alignment check where this would be needed. The advantage there is that old and new code would interoperate; the disadvantage is that an operation that previously could not panic, now can panic.

Returning to the presentation of the great idea...

The two magic comments would be

//go:align(<somevalue>)
//go:orderedfields

Here, <somevalue> specifies the byte alignment required for a type, and is one of

  • a power of two (not sure how large these should be allowed; can be higher or lower than default, subject to the compiler whining about lack of a load instruction.)
  • ptr to specify the native pointer alignment
  • the name of a language (C, Java, Fortran, ???) to specify the native alignment of the corresponding type in the other programming language, with an error if we believe the other language has no such corresponding type (Fortran has complex, Java does not).
  • Possibly "*" preceding an alignment to indicate a pointer to an aligned quantity; I am not convinced this is necessary, because a pointer to an aligned type can inherit the alignment. If this is allowed, there can be multiple alignment attributes for both the pointer itself and the referent.

Thus

type cacheLine       [64]uint64 //go:align(64)
type atomicComplex64 complex64  //go:align(8)
type floatPtrPun     float64    //go:align(ptr)

type cacheLinePtr *cacheLine // implicitly, a pointer to something that is aligned
type alignedPtrToAligned8Uint8 *[8]uint8 // go:align(8,*8)

and

type INPUT_keymap_entry struct { //go:orderedfields
	Flags uint8; 
	Len uint8;
	Index uint16; 
	Keycode uint32;
	Scancode [32]uint8;
};

and if you are not a trusting soul or wish to be explicit about your expectations

type INPUT_keymap_entry struct { //go:orderedfields
	Flags uint8;         //go:align(1)
	Len uint8;           //go:align(1)
	Index uint16;        //go:align(2)
	Keycode uint32;      //go:align(4)
	Scancode [32]uint8;  //go:align(1)
};

I expect the following uses to be common:

  • simple type, with alignment, gets that alignment wherever it is allocated, and is part of minimum alignment of any containing struct. Within an otherwise undecorated struct, field may be reordered to satisfy whims of compiler writers.
  • structures for interchange are ordered, if there's no ambiguity about field alignments (this is up to convention and expectations of code authors, vet might help define "ambiguity") then they don't need alignment annotations. But useless alignment annotations are fine.
  • Some types have non-negotiable alignment restrictions and it is a compile-time error to break those.

There is a problem with conversions from pointer-to-less-aligned-thing to pointer-to-more-aligned-thing. Pointer conversion currently is unchecked -- if the compiler allows it, it happens. If we allow such pointers (and we might want them for atomics, if we like static checking) then we need an answer for this. I think the least-surprise option would be to enhance the semantics of a plain pointer cast to include an alignment check where this would be needed. The advantage there is that old and new code would interoperate; the disadvantage is that an operation that previously could not panic, now can panic.

We could provide a tool that would pre-annotate structs/types for easy compatibility into the future, and the annotations are backwards compatible in the sense that old code, annotated, then runs in an old system, would behave as it always did. However, old code, annotated, might not run in a new system because of referent alignment issues in pointer conversions.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jan 28, 2020

Can you give an example where Go would permit a conversion from pointer to a less-aligned thing to a pointer to a more-aligned thing without using unsafe?

@dr2chase

This comment has been minimized.

Copy link
Contributor

@dr2chase dr2chase commented Jan 28, 2020

Statically, it would be unsafe. Dynamically, we could check. Maybe we call it unsafe for now, wait to see if anyone ever needs to do it. It wouldn't surprise me if "old" code mechanically ported forward would trip over this (in the static case).

Example, using the alignment annotation:

type T struct {
   x int64 // current default alignment for int64 is 32 bits, right?
}
type ptrAtomicInt64 *int //go:align(*8) // this is a pointer to an 8-byte-aligned 64-bit int.
...
v := new(T) // GC allocates on 8-byte boundary, so v.x is 8-byte aligned
pax := ptrAtomicInt64(&v.x) // statically dubious, a dynamic check would succeed
@beoran

This comment has been minimized.

Copy link

@beoran beoran commented Jan 28, 2020

What happens when a pointer to a 32 bit aligned struct is stored in an interface`, which presumably is 64 bits aligned internally on 32 bits architecture?

type Foo struct {
        Field int32
}
var foo = &Foo { 7 }
var ifa interface{} = foo
@randall77

This comment has been minimized.

Copy link
Contributor

@randall77 randall77 commented Jan 28, 2020

@ianlancetaylor Here's an example:

package main

type T int32 //go:align(8)

func upconvert(p *int32) *T {
	return (*T)(p)
}

If a 4-byte, but not 8-byte, aligned pointer were passed to upconvert, then the result would not obey the alignment constraints of T. If say, the *T was passed to assembly, it might assume 8-byte alignment, use an 8-byte aligned load instruction, and fail.

@beoran I'm not sure what you are asking. An interface is just a pair of pointers. They are aligned just as pointers are aligned (32 bits on 32 bit systems). In any case, the interface's alignment is irrelevant to the alignment of the thing it points to.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jan 29, 2020

@randall77 That seems like a great argument for only permitting //go:align on struct types.

@dr2chase

This comment has been minimized.

Copy link
Contributor

@dr2chase dr2chase commented Jan 29, 2020

@ianlancetaylor Or declaring that such alignment upconversions are unsafe -- and perhaps they are checked when they occur there (i.e., semantic change to a pointer cast that adds checking, but this can only occur in unsafe code).

My Great Idea may be a Great Idea™. (As a wise former colleague once remarked, the ™ means it isn't what it says it is. Would you rather have cheese, or Cheese™?)

@randall77

This comment has been minimized.

Copy link
Contributor

@randall77 randall77 commented Jan 29, 2020

The same problem occurs for struct types.

type T struct { //go:align(8)
	a, b int32
}
func upconvert(p *struct{ a, b int32 }) *T {
	return (*T)(p)
}
@danscales

This comment has been minimized.

Copy link
Author

@danscales danscales commented Jan 29, 2020

With the addition of something like '//go:align(8)' for types, could we declare that the byte alignment of a pointer type is now included in its underlying type? Therefore, pointers to otherwise identical struct types with different byte alignments are not longer convertible.

Hence in this example (where default alignment of A and B is 4 bytes):

type A struct {
  x, y, z, w int32
}

type B struct {
  x, y, z, w int32
}

//align:8
type C struct {
  x, y, z, w int32
}

a := A{}
b := (*B)(a)     // allowed
c := (*C)(a)    // not allowed

converting a pointer to A to a pointer to C is not allowed (compile-time error).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
10 participants
You can’t perform that action at this time.