mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-12 23:13:40 +00:00
cgo: implement C unions
Unions are somewhat hard to implement in Go because they are not a native type. But it is actually possible with some compiler magic. This commit inserts a special "C union" field at the start of a struct to indicate that it is a union. As such a field cannot be written directly in Go, this is a useful to distinguish structs and unions.
This commit is contained in:
committed by
Ron Evans
parent
536086988c
commit
d2b3a5486c
+57
-5
@@ -546,6 +546,33 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
|
||||
}
|
||||
members[i] = member
|
||||
}
|
||||
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), nil
|
||||
case *types.Tuple:
|
||||
members := make([]llvm.Type, typ.Len())
|
||||
@@ -1592,6 +1619,19 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
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, err := c.getLLVMType(expr.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
alloca := c.builder.CreateAlloca(value.Type(), "")
|
||||
c.builder.CreateStore(value, alloca)
|
||||
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "")
|
||||
return c.builder.CreateLoad(bitcast, ""), nil
|
||||
}
|
||||
result := c.builder.CreateExtractValue(value, expr.Field, "")
|
||||
return result, nil
|
||||
case *ssa.FieldAddr:
|
||||
@@ -1599,16 +1639,28 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
|
||||
}
|
||||
// Check for nil pointer before calculating the address, from the spec:
|
||||
// > For an operand x of type T, the address operation &x generates a
|
||||
// > 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")
|
||||
return c.builder.CreateGEP(val, indices, ""), nil
|
||||
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, err := c.getLLVMType(expr.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
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.CreateGEP(val, indices, ""), nil
|
||||
}
|
||||
case *ssa.Function:
|
||||
fn := c.ir.GetFunction(expr)
|
||||
if fn.IsExported() {
|
||||
|
||||
@@ -170,6 +170,10 @@ func getTypeCodeName(t types.Type) string {
|
||||
return "slice:" + name + 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++ {
|
||||
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||
}
|
||||
|
||||
+36
-3
@@ -63,6 +63,12 @@ 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())
|
||||
@@ -125,11 +131,38 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
return 0
|
||||
}
|
||||
fields := make([]*types.Var, t.NumFields())
|
||||
maxAlign := int64(1)
|
||||
for i := range fields {
|
||||
fields[i] = t.Field(i)
|
||||
field := t.Field(i)
|
||||
fields[i] = field
|
||||
al := s.Alignof(field.Type())
|
||||
if al > maxAlign {
|
||||
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)
|
||||
}
|
||||
offsets := s.Offsetsof(fields)
|
||||
return offsets[n-1] + s.Sizeof(fields[n-1].Type())
|
||||
case *types.Interface:
|
||||
return s.PtrSize * 2
|
||||
case *types.Pointer:
|
||||
|
||||
Reference in New Issue
Block a user