llir / llvm

Library for interacting with LLVM IR in pure Go.
https://llir.github.io/document/
BSD Zero Clause License
1.2k stars 79 forks source link

`sizeof`? #190

Open Nv7-GitHub opened 3 years ago

Nv7-GitHub commented 3 years ago

How do you call the sizeof function from LLVM? When C code using sizeof is generated, the code generated just has a constant. Is there a way to find the size of an object from LLVM?

mewmew commented 3 years ago

Hi @Nv7-GitHub,

There is an initial implementation for handling basic types (e.g. int, float, etc). See irutil.Layout and irutil.DefaultLayout.SizeOf. Note that the implementation does not yet handle calculating the size of composite types (e.g. arrays, structs, etc) as they depend on padding of the memory layout.

The intention is to parse LLVM data layout strings to determine the handling of padding in structs, arrays, etc. This would be used to implement the irutil.Layout interface and have it determine the size of composite types. This is tracked by issue #189.

For added background, see #66 for further background.

Cheers, Robin

Edit: @Nv7-GitHub should you feel like getting more involved with the llir/llvm project, consider implementing a parser for the LLVM data layout string. This would go a long way to adding support for determining the size of composite types (as covered by issue #189).

Nv7-GitHub commented 3 years ago

Are there any examples on using irutil.Layout?

mewmew commented 3 years ago

Are there any examples on using irutil.Layout?

Here is a rough usage example. It assumes 1-byte memory alignment. Preferably such information should be parsed from the LLVM IR data layout string (i.e. llir/llvm/ir.Module.DataLayout).

package main

import (
    "fmt"

    "github.com/llir/irutil"
    "github.com/llir/llvm/ir/types"
)

func main() {
    foo()
    // Output:
    //
    // type: i8 (size: 8 bits)
    // panic: support for size of on type *types.ArrayType not yet implemented

    bar()
    // Output:
    //
    // type: i8 (size: 8 bits)
    // type: [123 x i8] (size: 984 bits)
}

func foo() {
    defer func() {
        e := recover() // recover from panic "support for size of on type *types.ArrayType not yet implemented"
        if e != nil {
            fmt.Printf("recovered from panic: %v\n", e)
        }
    }()

    layout := irutil.DefaultLayout{}

    i8Type := types.I8
    i8Size := layout.SizeOf(i8Type)
    fmt.Printf("type: %v (size: %d bits)\n", i8Type, i8Size)
    // Output:
    //
    // type: i8 (size: 8 bits)

    arrayType := types.NewArray(123, i8Type)
    arraySize := layout.SizeOf(arrayType)
    fmt.Printf("type: %v (size: %d bits)\n", arrayType, arraySize)
    // Output:
    //
    // panic: support for size of on type *types.ArrayType not yet implemented
}

// Layout specifies how data is to be laid out in memory, using one-byte memory
// alignment for padding of struct fields and array elements.
type Layout struct {
    irutil.DefaultLayout
}

// SizeOf returns the size of the given type in number of bits.
func (l Layout) SizeOf(typ types.Type) int {
    const pointerSize = 8 // assume 8 byte pointer size
    switch typ := typ.(type) {
    case *types.VoidType:
        return 0
    case *types.FuncType:
        return pointerSize // assume function types are represented as pointers.
    case *types.MMXType:
        return 64 // MMX registers are 64 bits in size.
    case *types.PointerType:
        return pointerSize
    case *types.VectorType:
        return l.SizeOf(typ.ElemType) * int(typ.Len)
    case *types.LabelType:
        return 0 // TODO: figure out how to handle size of on label types.
    case *types.TokenType:
        return 0 // TODO: figure out how to handle size of on token types.
    case *types.MetadataType:
        return 0 // TODO: figure out how to handle size of on metadata types.
    case *types.ArrayType:
        return l.SizeOf(typ.ElemType) * int(typ.Len)
    case *types.StructType:
        total := 0
        // TODO: figure out how to handle Opaque struct types.
        // TODO: handle padding between struct fields if using other memory
        // alignment than 1 byte.
        align := 1 // TODO: read alignment from data layout of LLVM IR module.
        if typ.Packed {
            align = 1
        }
        for _, field := range typ.Fields {
            fieldSize := l.SizeOf(field)
            _ = align // TODO: handle alignment in between fields.
            total += fieldSize
        }
        return total
    }
    //case *types.IntType:   // handled by irutil.DefaultLayout
    //case *types.FloatType: // handled by irutil.DefaultLayout
    return l.DefaultLayout.SizeOf(typ)
}

func bar() {
    layout := Layout{}

    i8Type := types.I8
    i8Size := layout.SizeOf(i8Type)
    fmt.Printf("type: %v (size: %d bits)\n", i8Type, i8Size)
    // Output:
    //
    // type: i8 (size: 8 bits)

    arrayType := types.NewArray(123, i8Type)
    arraySize := layout.SizeOf(arrayType)
    fmt.Printf("type: %v (size: %d bits)\n", arrayType, arraySize)
    // Output:
    //
    // type: [123 x i8] (size: 984 bits)
}
dannypsnl commented 3 years ago

@mewmew Probably we can merge these howto to document?