Browse Source

cgo: refactor union support

Instead of putting the magic in the AST, generate regular accessor
methods. This avoids a number of special cases in the compiler, and
avoids missing any of them.

The resulting union accesses are somewhat clunkier to use, but the
compiler implementation has far less coupling between the CGo
implementation and the IR generator.
pull/703/head
Ayke van Laethem 5 years ago
committed by Ron Evans
parent
commit
6108ee6859
  1. 206
      cgo/cgo.go
  2. 123
      cgo/libclang.go
  3. 37
      cgo/testdata/types.go
  4. 31
      cgo/testdata/types.out.go
  5. 57
      compiler/compiler.go
  6. 4
      compiler/interface.go
  7. 35
      compiler/sizes.go
  8. 21
      testdata/cgo/main.go

206
cgo/cgo.go

@ -12,6 +12,7 @@ package cgo
// source file parsing.
import (
"fmt"
"go/ast"
"go/token"
"sort"
@ -69,9 +70,11 @@ type typedefInfo struct {
// elaboratedTypeInfo contains some information about an elaborated type
// (struct, union) found in the C AST.
type elaboratedTypeInfo struct {
typeExpr *ast.StructType
pos token.Pos
bitfields []bitfieldInfo
typeExpr *ast.StructType
pos token.Pos
bitfields []bitfieldInfo
unionSize int64 // union size in bytes, nonzero when union getters/setters should be created
unionAlign int64 // union alignment in bytes
}
// bitfieldInfo contains information about a single bitfield in a struct. It
@ -608,13 +611,31 @@ func (p *cgoPackage) addElaboratedTypes() {
Kind: ast.Typ,
Name: typeName,
}
typeExpr := typ.typeExpr
if typ.unionSize != 0 {
// Create getters/setters.
for _, field := range typ.typeExpr.Fields.List {
if len(field.Names) != 1 {
p.addError(typ.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names)))
continue
}
p.createUnionAccessor(field, typeName)
}
// Convert to a single-field struct type.
typeExpr = p.makeUnionField(typ)
if typeExpr == nil {
// There was an error, that was already added to the list of
// errors.
continue
}
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
Type: typ.typeExpr,
Type: typeExpr,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
@ -627,6 +648,183 @@ func (p *cgoPackage) addElaboratedTypes() {
}
}
// makeUnionField creates a new struct from an existing *elaboratedTypeInfo,
// that has just a single field that must be accessed through special accessors.
// It returns nil when there is an error. In case of an error, that error has
// already been added to the list of errors using p.addError.
func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType {
unionFieldTypeName, ok := map[int64]string{
1: "uint8",
2: "uint16",
4: "uint32",
8: "uint64",
}[typ.unionAlign]
if !ok {
p.addError(typ.typeExpr.Struct, fmt.Sprintf("expected union alignment to be one of 1, 2, 4, or 8, but got %d", typ.unionAlign))
return nil
}
var unionFieldType ast.Expr = &ast.Ident{
NamePos: token.NoPos,
Name: unionFieldTypeName,
}
if typ.unionSize != typ.unionAlign {
// A plain struct{uintX} isn't enough, we have to make a
// struct{[N]uintX} to make the union big enough.
if typ.unionSize/typ.unionAlign*typ.unionAlign != typ.unionSize {
p.addError(typ.typeExpr.Struct, fmt.Sprintf("union alignment (%d) must be a multiple of union alignment (%d)", typ.unionSize, typ.unionAlign))
return nil
}
unionFieldType = &ast.ArrayType{
Len: &ast.BasicLit{
Kind: token.INT,
Value: strconv.FormatInt(typ.unionSize/typ.unionAlign, 10),
},
Elt: unionFieldType,
}
}
return &ast.StructType{
Struct: typ.typeExpr.Struct,
Fields: &ast.FieldList{
Opening: typ.typeExpr.Fields.Opening,
List: []*ast.Field{&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: typ.typeExpr.Fields.Opening,
Name: "$union",
},
},
Type: unionFieldType,
}},
Closing: typ.typeExpr.Fields.Closing,
},
}
}
// createUnionAccessor creates a function that returns a typed pointer to a
// union field for each field in a union. For example:
//
// func (union *C.union_1) unionfield_d() *float64 {
// return (*float64)(unsafe.Pointer(&union.$union))
// }
//
// Where C.union_1 is defined as:
//
// type C.union_1 struct{
// $union uint64
// }
//
// The returned pointer can be used to get or set the field, or get the pointer
// to a subfield.
func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
if len(field.Names) != 1 {
panic("number of names in union field must be exactly 1")
}
fieldName := field.Names[0]
pos := fieldName.NamePos
// The method receiver.
receiver := &ast.SelectorExpr{
X: &ast.Ident{
NamePos: pos,
Name: "union",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: pos,
Name: "$union",
},
}
// Get the address of the $union field.
receiverPtr := &ast.UnaryExpr{
Op: token.AND,
X: receiver,
}
// Cast to unsafe.Pointer.
sourcePointer := &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "unsafe"},
Sel: &ast.Ident{Name: "Pointer"},
},
Args: []ast.Expr{receiverPtr},
}
// Cast to the target pointer type.
targetPointer := &ast.CallExpr{
Lparen: pos,
Fun: &ast.ParenExpr{
Lparen: pos,
X: &ast.StarExpr{
X: field.Type,
},
Rparen: pos,
},
Args: []ast.Expr{sourcePointer},
Rparen: pos,
}
// Create the accessor function.
accessor := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "union",
},
},
Type: &ast.StarExpr{
Star: pos,
X: &ast.Ident{
NamePos: pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: pos,
},
Name: &ast.Ident{
NamePos: pos,
Name: "unionfield_" + fieldName.Name,
},
Type: &ast.FuncType{
Func: pos,
Params: &ast.FieldList{
Opening: pos,
Closing: pos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: &ast.StarExpr{
Star: pos,
X: field.Type,
},
},
},
},
},
Body: &ast.BlockStmt{
Lbrace: pos,
List: []ast.Stmt{
&ast.ReturnStmt{
Return: pos,
Results: []ast.Expr{
targetPointer,
},
},
},
Rbrace: pos,
},
}
p.generated.Decls = append(p.generated.Decls, accessor)
}
// createBitfieldGetter creates a bitfield getter function like the following:
//
// func (s *C.struct_foo) bitfield_b() byte {

123
cgo/libclang.go

@ -506,44 +506,37 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
var cgoRecordPrefix string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoRecordPrefix = "struct_"
case C.CXCursor_UnionDecl:
cgoRecordPrefix = "union_"
default:
// makeASTRecordType will create an appropriate error.
cgoRecordPrefix = "record_"
}
if name == "" {
// Anonymous record, probably inside a typedef.
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
if bitfieldList != nil {
// This struct has bitfields, so we have to declare it as a
// named type (for bitfield getters/setters to work).
typeInfo := p.makeASTRecordType(cursor, pos)
if typeInfo.bitfields != nil || typeInfo.unionSize != 0 {
// This record is a union or is a struct with bitfields, so we
// have to declare it as a named type (for getters/setters to
// work).
p.anonStructNum++
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = typeInfo
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
return typeExpr
return typeInfo.typeExpr
} else {
var cgoName string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoName = "struct_" + name
case C.CXCursor_UnionDecl:
cgoName = "union_" + name
default:
// makeASTRecordType will create an appropriate error.
cgoName = "record_" + name
}
cgoName := cgoRecordPrefix + name
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos)
}
return &ast.Ident{
NamePos: pos,
@ -591,9 +584,8 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
// makeASTRecordType parses a C record (struct or union) and translates it into
// a Go struct type. Unions are implemented by setting the first field to a
// zero-lengt "C union" field, which cannot be written in Go directly.
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
// a Go struct type.
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
@ -613,53 +605,50 @@ func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast
renameFieldKeywords(fieldList)
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
return &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
bitfields: bitfieldList,
}
case C.CXCursor_UnionDecl:
typeInfo := &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
bitfields: bitfieldList,
}
if len(fieldList.List) <= 1 {
// Useless union, treat it as a regular struct.
return typeInfo
}
if bitfieldList != nil {
// This is valid C... but please don't do this.
p.addError(pos, "bitfield in a union is not supported")
}
if len(fieldList.List) > 1 {
// Insert a special field at the front (of zero width) as a
// marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: pos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
typ := C.tinygo_clang_getCursorType(cursor)
alignInBytes := int64(C.clang_Type_getAlignOf(typ))
sizeInBytes := int64(C.clang_Type_getSizeOf(typ))
if sizeInBytes == 0 {
p.addError(pos, "zero-length union is not supported")
}
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
typeInfo.unionSize = sizeInBytes
typeInfo.unionAlign = alignInBytes
return typeInfo
default:
cursorKind := C.tinygo_clang_getCursorKind(cursor)
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
return &ast.StructType{
Struct: pos,
}, nil
return &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
},
pos: pos,
}
}
}

37
cgo/testdata/types.go

@ -27,6 +27,25 @@ struct type2 {
int _type;
};
// Unions.
typedef union {
// Union should be treated as a struct.
int i;
} union1_t;
typedef union {
// Union must contain a single field and have special getters/setters.
int i;
double d;
short s;
} union3_t;
typedef union union2d {
int i;
double d[2];
} union2d_t;
typedef union {
unsigned char arr[10];
} unionarrary_t;
// Enums. These define constant numbers. All these constants must be given the
// correct number.
typedef enum option {
@ -85,6 +104,12 @@ var (
_ C.struct_type1
_ C.struct_type2
// Unions.
_ C.union1_t
_ C.union3_t
_ C.union2d_t
_ C.unionarrary_t
// Enums (anonymous and named).
_ C.option_t
_ C.enum_option
@ -98,7 +123,7 @@ var (
)
// Test bitfield accesses.
func foo() {
func accessBitfields() {
var x C.bitfield_t
x.start = 3
x.set_bitfield_a(4)
@ -108,3 +133,13 @@ func foo() {
x.e = 5
var _ C.uchar = x.bitfield_a()
}
// Test union accesses.
func accessUnion() {
var union1 C.union1_t
union1.i = 5
var union2d C.union2d_t
var _ *C.int = union2d.unionfield_i()
var _ *[2]float64 = union2d.unionfield_d()
}

31
cgo/testdata/types.out.go

@ -34,7 +34,7 @@ type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
type C.bitfield_t = C.struct_1
type C.bitfield_t = C.struct_2
type C.myIntArray = [10]C.int
type C.myint = C.int
type C.option2_t = C.uint
@ -49,21 +49,25 @@ type C.types_t = struct {
d float64
ptr *C.int
}
type C.union1_t = struct{ i C.int }
type C.union2d_t = C.union_union2d
type C.union3_t = C.union_1
type C.unionarrary_t = struct{ arr [10]C.uchar }
func (s *C.struct_1) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *C.struct_1) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 }
func (s *C.struct_1) bitfield_b() C.uchar {
func (s *C.struct_2) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *C.struct_2) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 }
func (s *C.struct_2) bitfield_b() C.uchar {
return s.__bitfield_1 >> 5 & 0x1
}
func (s *C.struct_1) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 }
func (s *C.struct_1) bitfield_c() C.uchar {
func (s *C.struct_2) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 }
func (s *C.struct_2) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6
}
func (s *C.struct_1) set_bitfield_c(value C.uchar,
func (s *C.struct_2) set_bitfield_c(value C.uchar,
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
type C.struct_1 struct {
type C.struct_2 struct {
start C.uchar
__bitfield_1 C.uchar
@ -81,5 +85,16 @@ type C.struct_type1 struct {
___type C.int
}
type C.struct_type2 struct{ _type C.int }
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
type C.union_1 struct{ $union uint64 }
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) }
type C.union_union2d struct{ $union [2]uint64 }
type C.enum_option C.int
type C.enum_unused C.uint

57
compiler/compiler.go

@ -493,33 +493,6 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
for i := 0; i < typ.NumFields(); i++ {
members[i] = c.getLLVMType(typ.Field(i).Type())
}
if len(members) > 2 && typ.Field(0).Name() == "C union" {
// Not a normal struct but a C union emitted by cgo.
// Such a field name cannot be entered in regular Go code, this must
// be manually inserted in the AST so this is safe.
maxAlign := 0
maxSize := uint64(0)
mainType := members[0]
for _, member := range members {
align := c.targetData.ABITypeAlignment(member)
size := c.targetData.TypeAllocSize(member)
if align > maxAlign {
maxAlign = align
mainType = member
} else if align == maxAlign && size > maxSize {
maxAlign = align
maxSize = size
mainType = member
} else if size > maxSize {
maxSize = size
}
}
members = []llvm.Type{mainType}
mainTypeSize := c.targetData.TypeAllocSize(mainType)
if mainTypeSize < maxSize {
members = append(members, llvm.ArrayType(c.ctx.Int8Type(), int(maxSize-mainTypeSize)))
}
}
return c.ctx.StructType(members, false)
case *types.Tuple:
members := make([]llvm.Type, typ.Len())
@ -1478,18 +1451,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
return c.builder.CreateExtractValue(value, expr.Index, ""), nil
case *ssa.Field:
value := c.getValue(frame, expr.X)
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
// Extract a field from a CGo union.
// This could be done directly, but as this is a very infrequent
// operation it's much easier to bitcast it through an alloca.
resultType := c.getLLVMType(expr.Type())
alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(value.Type(), "union.alloca")
c.builder.CreateStore(value, alloca)
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "union.bitcast")
result := c.builder.CreateLoad(bitcast, "union.result")
c.emitLifetimeEnd(allocaPtr, allocaSize)
return result, nil
}
result := c.builder.CreateExtractValue(value, expr.Field, "")
return result, nil
case *ssa.FieldAddr:
@ -1499,20 +1460,12 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
// > pointer of type *T to x. [...] If the evaluation of x would cause a
// > run-time panic, then the evaluation of &x does too.
c.emitNilCheck(frame, val, "gep")
if s := expr.X.Type().(*types.Pointer).Elem().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
// This is not a regular struct but actually an union.
// That simplifies things, as we can just bitcast the pointer to the
// right type.
ptrType := c.getLLVMType(expr.Type())
return c.builder.CreateBitCast(val, ptrType, ""), nil
} else {
// Do a GEP on the pointer to get the field address.
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
}
return c.builder.CreateInBoundsGEP(val, indices, ""), nil
// Do a GEP on the pointer to get the field address.
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
}
return c.builder.CreateInBoundsGEP(val, indices, ""), nil
case *ssa.Function:
panic("function is not an expression")
case *ssa.Global:

4
compiler/interface.go

@ -210,10 +210,6 @@ func getTypeCodeName(t types.Type) string {
return "slice:" + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
// TODO: report this as a normal error instead of panicking.
panic("cgo unions are not allowed in interfaces")
}
for i := 0; i < t.NumFields(); i++ {
embedded := ""
if t.Field(i).Embedded() {

35
compiler/sizes.go

@ -63,12 +63,6 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
if len(fields) > 1 && fields[0].Name() == "C union" {
// This struct contains the magic "C union" field which indicates that
// this is actually a union from CGo.
// All fields in the union start at 0 so return that.
return offsets // all fields are still set to 0
}
var o int64
for i, f := range fields {
a := s.Alignof(f.Type())
@ -143,29 +137,12 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
maxAlign = al
}
}
if fields[0].Name() == "C union" {
// Magic field that indicates this is a CGo union and not a struct.
// The size is the biggest element, aligned to the element with the
// biggest alignment. This is not necessarily the same, for example
// in the following union:
// union { int32_t l; int16_t s[3] }
maxSize := int64(0)
for _, field := range fields[1:] {
si := s.Sizeof(field.Type())
if si > maxSize {
maxSize = si
}
}
return align(maxSize, maxAlign)
} else {
// This is a regular struct.
// Pick the size that fits this struct and add some alignment. Some
// structs have some extra padding at the end which should also be
// taken care of:
// struct { int32 n; byte b }
offsets := s.Offsetsof(fields)
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
}
// Pick the size that fits this struct and add some alignment. Some
// structs have some extra padding at the end which should also be taken
// care of:
// struct { int32 n; byte b }
offsets := s.Offsetsof(fields)
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
case *types.Interface:
return s.PtrSize * 2
case *types.Pointer:

21
testdata/cgo/main.go

@ -57,12 +57,14 @@ func main() {
println("array:", C.globalArray[0], C.globalArray[1], C.globalArray[2])
println("union:", C.int(unsafe.Sizeof(C.globalUnion)) == C.globalUnionSize)
C.unionSetShort(22)
println("union s:", C.globalUnion.s)
println("union s:", *C.globalUnion.unionfield_s())
C.unionSetFloat(3.14)
println("union f:", C.globalUnion.f)
println("union f:", *C.globalUnion.unionfield_f())
C.unionSetData(5, 8, 1)
println("union global data:", C.globalUnion.data[0], C.globalUnion.data[1], C.globalUnion.data[2])
println("union field:", printUnion(C.globalUnion).f)
data := C.globalUnion.unionfield_data()
println("union global data:", data[0], data[1], data[2])
returnedUnion := printUnion(C.globalUnion)
println("union field:", *returnedUnion.unionfield_f())
var _ C.union_joined = C.globalUnion
printBitfield(&C.globalBitfield)
C.globalBitfield.set_bitfield_a(7)
@ -105,11 +107,12 @@ func main() {
}
func printUnion(union C.joined_t) C.joined_t {
println("union local data: ", union.data[0], union.data[1], union.data[2])
union.s = -33
println("union s:", union.data[0] == -33)
union.f = 6.28
println("union f:", union.f)
data := union.unionfield_data()
println("union local data: ", data[0], data[1], data[2])
*union.unionfield_s() = -33
println("union s:", data[0] == -33)
*union.unionfield_f() = 6.28
println("union f:", *union.unionfield_f())
return union
}

Loading…
Cancel
Save