Skip to content

Instantly share code, notes, and snippets.

@ysbaddaden
Last active May 24, 2024 12:55
Show Gist options
  • Save ysbaddaden/cf58d33e316067a9e4ad886976cbdc98 to your computer and use it in GitHub Desktop.
Save ysbaddaden/cf58d33e316067a9e4ad886976cbdc98 to your computer and use it in GitHub Desktop.
Parse LLVMDataLayout string + alignof, sizeof & offsetof ints, floats & aggregates + ABI for Crystal::Type(s)
require "./data_layout"
# I must require the WHOLE COMPILER just to load crystal/types, and then it
# takes as long as compiling the compiler to compile just this file and to do
# nothing.
require "compiler/requires"
class Crystal::ABI
@nil_type : DataLayout::Aggregate?
@proc_type : DataLayout::Aggregate?
def initialize(@data_layout : DataLayout)
# OPTIMIZE: are Crystal::Type unique across a compilation? in which case the
# cache key could be the class itself (no need to build a String
# everytime)
@cache = {} of String => DataLayout::Aggregate
end
# helpers
# the nil type is represented as an empty struct
protected def nil_type
@nil_type ||= @data_layout.aggregate
end
# proc types are represented as a pair of void pointers
protected def proc_type
@proc_type ||= @data_layout.aggregate.tap do |aggregate|
aggregate.add @data_layout.size_of_pointer, @data_layout.align_of_pointer
aggregate.add @data_layout.size_of_pointer, @data_layout.align_of_pointer
end
end
protected def aggregate_for(type : TupleInstanceType)
@cache[type.to_s] ||= @data_layout.aggregate(type.packed?).tap do |aggregate|
type.tuple_types.each do |t|
aggregate.add size_of(t) * 8, align_of(t) * 8
end
end
end
protected def aggregate_for(type : NamedTupleInstanceType)
@cache[type.to_s] ||= @data_layout.aggregate(type.packed?).tap do |aggregate|
type.entries.each do |entry|
aggregate.add size_of(entry.type) * 8, align_of(entry.type) * 8
end
end
end
protected def aggregate_for(type : InstanceVarContainer)
@cache[type.to_s] ||= @data_layout.aggregate(type.packed?).tap do |aggregate|
type.all_instance_vars.each do |(_, t)|
aggregate.add size_of(t) * 8, align_of(t) * 8
end
end
end
protected def aggregate_for(type : Type) : NoReturn
raise "BUG: can't build aggregate for #{type}"
end
# alignof
def align_of(type : NoReturnType) : UInt32
1_u32
end
def align_of(type : VoidType) : UInt32
# We need `alignof(Void)` to be 1 because it is effectively the smallest
# possible alignment for any type.
1_u32
end
def align_of(type : NilType) : UInt32
nil_type.alignment // 8
end
def align_of(type : BoolType) : UInt32
@data_layout.align_of_int(1) // 8
end
def align_of(type : CharType) : UInt32
@data_layout.align_of_int(32) // 8
end
def align_of(type : IntegerType) : UInt32
@data_layout.align_of_int(type.bits) // 8
end
def align_of(type : FloatType) : UInt32
@data_layout.align_of_float(type.bytes * 8) // 8
end
def align_of(type : SymbolType) : UInt32
@data_layout.align_of_int(32) // 8
end
def align_of(type : EnumType) : UInt32
align_of type.base_type
end
def align_of(type : ProcInstanceType | NilableProcType) : UInt32
proc_type.alignment // 8
end
def align_of(type : InstanceVarContainer) : UInt32
if type.struct?
instance_align_of(type)
else
# references are pointers
@data_layout.align_of_pointer // 8
end
end
def align_of(type : LibType) : UInt32
@data_layout.align_of_int(32) // 8
end
def align_of(type : MetaclassType | GenericClassInstanceMetaclassType | GenericModuleInstanceMetaclassType | VirtualMetaclassType) : UInt32
@data_layout.align_of_int(32) // 8
end
def align_of(type : PointerInstanceType) : UInt32
@data_layout.align_of_pointer // 8
end
def align_of(type : StaticArrayInstanceType) : UInt32
align_of type.element_type
end
def align_of(type : TupleInstanceType | NamedTupleInstanceType) : UInt32
aggregate_for(type).alignment // 8
end
def align_of(type : NilableType) : UInt32
align_of type.not_nil_type
end
def align_of(type : ReferenceUnionType | NilableReferenceUnionType) : UInt32
@data_layout.align_of_pointer # i32*
end
def align_of(type : TypeDefType) : UInt32
align_of type.typedef
end
def align_of(type : VirtualType) : UInt32
@data_layout.align_of_pointer // 8 # i32*
end
def align_of(type : AliasType) : UInt32
align_of type.remove_alias
end
def align_of(type : ReferenceStorageType) : UInt32
align_of type.reference_type
end
def align_of(type : NonGenericModuleType | GenericClassType) : UInt32
@data_layout.align_of_int(1) // 8
end
def align_of(type : Type) : NoReturn
raise "BUG: can't alignof #{type}"
end
private def align_of(type : MetaTypeVar) : UInt32
align_of type.type
end
# instance_alignof
def instance_align_of(type : InstanceVarContainer) : UInt32
type = type.remove_indirection
if type.extern_union?
# largest alignment from all the union types
type.instance_vars.reduce(0_u32) do |max, (_, t)|
align = align_of(t)
align > max ? align : max
end
else
aggregate_for(type).alignment // 8
end
end
def instance_align_of(type : Type) : NoReturn
raise "BUG: can't instance_alignof #{type}"
end
# sizeof
def size_of(type : NoReturnType) : UInt64
0_u64
end
def size_of(type : VoidType) : UInt64
# We need `sizeof(Void)` to be 1 because doing `Pointer(Void).malloc` must
# work like `Pointer(UInt8).malloc`, that is, consider Void like the size
# of a byte.
1_u64
end
def size_of(type : NilType) : UInt64
nil_type.size // 8
end
def size_of(type : BoolType) : UInt64
@data_layout.size_of_int(1) // 8
end
def size_of(type : CharType) : UInt64
@data_layout.size_of_int(32) // 8
end
def size_of(type : IntegerType) : UInt64
@data_layout.size_of_int(type.bits) // 8
end
def size_of(type : FloatType) : UInt64
@data_layout.size_of_float(type.bytes * 8) // 8
end
def size_of(type : SymbolType) : UInt64
@data_layout.size_of_int(32) // 8
end
def size_of(type : EnumType) : UInt64
size_of type.base_type
end
def size_of(type : ProcInstanceType | NilableProcType) : UInt64
proc_type.size // 8
end
private def size_of(type : MetaTypeVar) : UInt64
size_of type.type
end
def size_of(type : InstanceVarContainer) : UInt64
if type.struct?
instance_size_of(type)
else
# references are mere pointers
@data_layout.size_of_pointer // 8
end
end
def size_of(type : LibType) : UInt64
@data_layout.size_of_int(32) // 8
end
def size_of(type : MetaclassType | GenericClassInstanceMetaclassType | GenericModuleInstanceMetaclassType | VirtualMetaclassType) : UInt64
@data_layout.size_of_int(32) // 8
end
def size_of(type : PointerInstanceType) : UInt64
@data_layout.size_of_pointer // 8
end
def size_of(type : StaticArrayInstanceType) : UInt64
size_of(type.element_type) * type.size.as(NumberLiteral).value.to_u64
end
def size_of(type : TupleInstanceType | NamedTupleInstanceType) : UInt64
aggregate_for(type).size // 8
end
def size_of(type : NilableType) : UInt64
size_of type.not_nil_type
end
def size_of(type : ReferenceUnionType | NilableReferenceUnionType) : UInt64
@data_layout.size_of_pointer // 8 # i32*
end
def size_of(type : TypeDefType) : UInt64
size_of type.typedef
end
def size_of(type : VirtualType) : UInt64
@data_layout.size_of_pointer // 8 # i32*
end
def size_of(type : AliasType) : UInt64
size_of type.remove_alias
end
def size_of(type : ReferenceStorageType) : UInt64
size_of type.reference_type
end
def size_of(type : NonGenericModuleType | GenericClassType) : UInt64
@data_layout.size_of_int(1) // 8
end
def size_of(type : Type) : UInt64
raise "BUG: can't sizeof #{type}"
end
# instance_sizeof
def instance_size_of(type : InstanceVarContainer) : UInt64
type = type.remove_indirection
if type.extern_union?
# largest type from all the union types
type.instance_vars.reduce(0_u64) do |max, (_, t)|
size = size_of(t)
size > max ? size : max
end
else
aggregate_for(type).size // 8
end
end
def instance_size_of(type : Type) : NoReturn
raise "BUG: can't instance_sizeof #{type}"
end
# offsetof
def offset_of(type : StaticArrayInstanceType, index : Int32) : UInt64
0_u64
end
def offset_of(type : TupleInstanceType | NamedTupleInstanceType, index : Int32) : UInt64
aggregate_for(type).offset(index) // 8
end
def offset_of(type : InstanceVarContainer, index : Int32) : UInt64
if type.extern_union?
0_u64
else
raise "BUG: can't compute offsetof #{type}"
end
end
def offset_of(type : Type) : NoReturn
raise "BUG: can't offsetof #{type}"
end
# instance_offsetof
def instance_offset_of(type : InstanceVarContainer, index : Int32) : UInt64
type = type.remove_indirection
if type.extern_union?
raise "BUG: can't compute instance_offsetof #{type}"
else
aggregate_for(type).offset(index) // 8
end
end
def instance_offset_of(type : Type) : NoReturn
raise "BUG: can't instance_offsetof #{type}"
end
end
require "./abi"
require "spec"
require "llvm"
LLVM.init_x86
private def semantic(source : String, *, flags = nil)
program = Crystal::Program.new
program.color = false
program.wants_doc = false
program.flags.concat(flags.split) if flags
parser = Crystal::Parser.new(source, warnings: nil)
parser.wants_doc = false
node = parser.parse
node = program.normalize(node)
node = program.semantic(node)
node
end
private ABI_CACHE = {} of String => Crystal::ABI
private TRIPLE = "x86_64-linux-gnu"
private def abi_for(triple : String)
ABI_CACHE[triple] ||= begin
machine = LLVM::Target.from_triple(triple).create_target_machine(triple)
data_layout = DataLayout.new(machine.data_layout.to_data_layout_string)
Crystal::ABI.new(data_layout)
end
end
describe "ABI" do
it "NumberLiteral" do
abi = abi_for(TRIPLE)
node = semantic("1_u8")
p! abi.align_of(node.type)
p! abi.size_of(node.type)
end
# TODO: write actual specs
end
# Parses a LLVM data layout string, and implements low level support for
# calculating the alignment and size of simple types (integers, floats,
# aggregates) as well as the offset of each element of an aggregate.
#
# NOTE: all sizes are in bits!
# FIXME: all sizes shall be in bytes to avoid lots of divide and multiply by 8
# to convert + avoid confusing when ABI usually needs bytes but the
# memory layout uses bits
# TODO: vectors (SIMD)
class DataLayout
enum Mangling : Int32
{% begin %}
ELF = {{'e'.ord}}
GOFF = {{'l'.ord}}
Mips = {{'m'.ord}}
Mach_O = {{'o'.ord}}
Windows_x86_COFF = {{'x'.ord}}
Windows_COFF = {{'w'.ord}}
XCOFF = {{'a'.ord}}
{% end %}
UNKNOWN = -1
end
enum FunctionPointerAlignment : Int32
{% begin %}
Implicit = {{'i'.ord}}
Explicit = {{'n'.ord}}
{% end %}
end
alias PointerAlignment = {address_space: Int32, size: Int32, abi_alignment: UInt32, preferred_alignment: UInt32, index_size: Int32}
alias SizeAlignment = {size: Int32, abi_alignment: UInt32, preferred_alignment: UInt32}
alias AggregateAlignment = {abi_alignment: UInt32, preferred_alignment: UInt32}
# :nodoc:
DEFAULT_INTEGER_ALIGNMENTS = StaticArray[
{size: 8, abi_alignment: 8_u32, preferred_alignment: 8_u32},
{size: 16, abi_alignment: 16_u32, preferred_alignment: 16_u32},
{size: 32, abi_alignment: 32_u32, preferred_alignment: 32_u32},
{size: 64, abi_alignment: 32_u32, preferred_alignment: 32_u32},
]
# :nodoc:
DEFAULT_FLOAT_ALIGNMENTS = StaticArray[
{size: 16, abi_alignment: 16_u32, preferred_alignment: 16_u32},
{size: 32, abi_alignment: 32_u32, preferred_alignment: 32_u32},
{size: 64, abi_alignment: 64_u32, preferred_alignment: 64_u32},
{size: 128, abi_alignment: 128_u32, preferred_alignment: 128_u32},
]
# :nodoc:
DEFAULT_POINTER = {size: 64, abi_alignment: 64_u32, preferred_alignment: 64_u32, index_size: 64}
# :nodoc:
DEFAULT_AGGREGATE = {abi_alignment: 8_u32, preferred_alignment: 8_u32}
getter! endianness : IO::ByteFormat
getter! natural_stack_alignment : Int32
getter program_address_space = 0
getter globals_address_space = 0
getter alloca_address_space = 0
getter pointer_alignments = Array(PointerAlignment).new
getter integer_alignments = Array(SizeAlignment).new
getter vector_alignments = Array(SizeAlignment).new
getter float_alignments = Array(SizeAlignment).new
getter! aggregate_alignment : AggregateAlignment
getter! mangling : Mangling
getter native_integer_widths = Array(Int32).new
getter non_integral_pointers = Array(Int32).new
getter! function_pointer_alignment : FunctionPointerAlignment
getter! function_pointer_multiplier : Int32?
def initialize(data_layout_string : String)
data_layout_string.split('-') do |str|
case str
when "e"
@endianness = IO::ByteFormat::LittleEndian
when "E"
@endianness = IO::ByteFormat::BigEndian
when .starts_with?('S')
@natural_stack_alignment = str[1..].to_i
when .starts_with?('P')
@program_address_space = str[1..].to_i
when .starts_with?('G')
@globals_address_space = str[1..].to_i
when .starts_with?('A')
@alloca_address_space = str[1..].to_i
when .starts_with?('p')
@pointer_alignments << parse_pointer_alignment(str)
when .starts_with?('i')
@integer_alignments << parse_size_alignment(str)
when .starts_with?('v')
@vector_alignments << parse_size_alignment(str)
when .starts_with?('f')
@float_alignments << parse_size_alignment(str)
when .starts_with?('a')
@aggregate_alignment = parse_aggregate_alignment(str)
when .starts_with?('F')
@function_pointer_alignment = FunctionPointerAlignment.new(str[1].ord)
@function_pointer_multiplier = str[2..].to_i
when .starts_with?('m')
@mangling = Mangling.new(str[2].ord)
when .starts_with?("ni:")
str[3..].split(':').each { |n| @non_integral_pointers << n.to_i }
when .starts_with?('n')
str[1..].split(':').each { |n| @native_integer_widths << n.to_i }
end
end
@pointer_alignments.sort_by! { |x| x[:address_space] }
@integer_alignments.sort_by! { |x| x[:size] }
@vector_alignments.sort_by! { |x| x[:size] }
@float_alignments.sort_by! { |x| x[:size] }
@native_integer_widths.sort!
end
private def parse_pointer_alignment(str)
parts = str[1..].split(':')
size = parts[1].to_i
abi_alignment = parts[2].to_u32
{
address_space: parts[0].to_i? || 0,
size: size,
abi_alignment: abi_alignment,
preferred_alignment: parts[3]?.try(&.to_u32?) || abi_alignment,
index_size: parts[4]?.try(&.to_i?) || size,
}
end
private def parse_size_alignment(str)
parts = str[1..].split(':')
size = parts[0].to_i
abi_alignment = parts[1].to_u32
{
size: size,
abi_alignment: abi_alignment,
preferred_alignment: parts[2]?.try(&.to_u32?) || abi_alignment,
}
end
private def parse_aggregate_alignment(str)
parts = str[2..].split(':')
abi_alignment = parts[0].to_u32
{
abi_alignment: abi_alignment,
preferred_alignment: parts[1]?.try(&.to_u32?) || abi_alignment,
}
end
# Computes the size and alignment of an aggregate, as well as the offset of
# each element.
#
# NOTE: all sizes are in bits!
# FIXME: all sizes shall be in bytes to avoid lots of divide and multiply by 8
# to convert + avoid confusing when ABI usually needs bytes but the
# memory layout uses bits
class Aggregate
protected def initialize(@default_alignment : UInt32, @packed : Bool)
@size = 0_u64
@alignment = @default_alignment
@offsets = [] of UInt64
end
def add(size : UInt64, align : UInt32) : Nil
offset = @size
if @packed
@size += size
else
@alignment = align if align > @alignment
offset = DataLayout.aligned_size(@size, align)
@size = offset + size
end
@offsets << offset
end
def alignment : UInt32
@alignment
end
def size : UInt64
DataLayout.aligned_size(@size, @alignment)
end
def offset(index : UInt32) : UInt64
@offsets[index]
end
end
def aggregate(packed : Bool = false) : Aggregate
Aggregate.new(default_align_of_aggregate, packed)
end
# align_of
def align_of_int(bits) : UInt32
align_of(bits, @integer_alignments, DEFAULT_INTEGER_ALIGNMENTS)
end
def align_of_float(bits) : UInt32
align_of(bits, @float_alignments, DEFAULT_FLOAT_ALIGNMENTS)
end
private def align_of(bits, specified, defaults) : UInt32
specified.each do |a|
return a[:abi_alignment] if bits == a[:size]
end
defaults.each do |a|
if bits <= a[:size]
c = specified.find { |x| x[:size] == a[:size] }
return (c || a)[:abi_alignment]
end
end
specified.each do |a|
return a[:abi_alignment] if bits < a[:size]
end
a = specified.last? || defaults.last
a[:abi_alignment]
end
def align_of_pointer(address_space = 0) : UInt32
if a = @pointer_alignments.find { |x| x[:address_space] == address_space }
a[:abi_alignment]
else
DEFAULT_POINTER[:abi_alignment]
end
end
# size_of
def size_of_int(bits) : UInt64
DataLayout.aligned_size(bits.to_u64, align_of_int(bits))
end
def size_of_float(bits) : UInt64
DataLayout.aligned_size(bits.to_u64, align_of_float(bits))
end
def size_of_pointer(address_space = 0) : UInt64
if a = @pointer_alignments.find { |x| x[:address_space] == address_space }
a[:size].to_u64
else
DEFAULT_POINTER[:size].to_u64
end
end
# helpers
protected def default_align_of_aggregate : UInt32
(@aggregate_alignment || DEFAULT_AGGREGATE)[:abi_alignment].to_u32.clamp(8_u32..)
end
protected def self.aligned_size(bits, align)
if bits % align == 0
bits
else
(bits // align + 1) * align
end
end
end
require "spec"
require "llvm"
require "./data_layout"
LLVM.init_aarch64
LLVM.init_arm
LLVM.init_avr
LLVM.init_webassembly
LLVM.init_x86
private LAYOUTS_CACHE = {} of String => {LLVM::Context, LLVM::TargetData, DataLayout}
private def machine_for(triple, &)
data = LAYOUTS_CACHE[triple] ||= begin
target = LLVM::Target.from_triple(triple)
machine = target.create_target_machine(triple)
ctx = LLVM::Context.new
target_data = machine.data_layout
data_layout = DataLayout.new(target_data.to_data_layout_string)
{ctx, target_data, data_layout}
end
yield(*data)
end
describe "memory layout" do
{% for triple in %w[
aarch64-linux-gnu
aarch64-apple-darwin
arm-linux-gnu
armv7-linux-gnu
avr-unknown-unknown
i686-linux-gnu
i686-windows-msvc
wasm32-unknown-wasi
x86_64-linux-gnu
x86_64-windows-msvc
x86_64-apple-darwin
] %}
describe {{triple}} do
describe "align_of" do
it "integers" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
[1, 7, 8, 15, 16, 24, 32, 64, 65, 128, 256].each do |n|
crystal_layout.align_of_int(n).should eq llvm_layout.abi_alignment(ctx.int(n)) * 8
end
end
end
it "floats" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
crystal_layout.align_of_float(16).should eq llvm_layout.abi_alignment(ctx.half) * 8
crystal_layout.align_of_float(32).should eq llvm_layout.abi_alignment(ctx.float) * 8
crystal_layout.align_of_float(64).should eq llvm_layout.abi_alignment(ctx.double) * 8
{% if triple.starts_with?("x86_64") || triple =~ /^i[3456]?86/ %}
crystal_layout.align_of_float(80).should eq llvm_layout.abi_alignment(ctx.x86_fp80) * 8
{% end %}
crystal_layout.align_of_float(128).should eq llvm_layout.abi_alignment(ctx.fp128) * 8
end
end
describe "pointer" do
it "default address space (0)" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
crystal_layout.align_of_pointer.should eq llvm_layout.abi_alignment(ctx.void_pointer) * 8
end
end
it "specific address spaces" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
crystal_layout.pointer_alignments.each do |x|
align = crystal_layout.align_of_pointer(x[:address_space])
align.should eq llvm_layout.abi_alignment(ctx.void_pointer(x[:address_space])) * 8
end
end
end
end
describe "aggregate" do
it "empty struct" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
p! agg = crystal_layout.aggregate
agg.alignment.should eq llvm_layout.abi_alignment(ctx.struct([] of LLVM::Type)) * 8
end
end
it "one property" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add(crystal_layout.size_of_int(32), crystal_layout.align_of_int(32))
agg.alignment.should eq llvm_layout.abi_alignment(ctx.struct([ctx.int32] of LLVM::Type)) * 8
end
end
it "a few properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
agg.alignment.should eq llvm_layout.abi_alignment(ctx.struct([
ctx.int8,
ctx.int32,
ctx.int16,
] of LLVM::Type)) * 8
end
end
it "large properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(512), crystal_layout.align_of_int(512)
agg.alignment.should eq llvm_layout.abi_alignment(ctx.struct([
ctx.int8,
ctx.int(512),
] of LLVM::Type)) * 8
end
end
it "many properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_float(32),crystal_layout.align_of_float(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
agg.add crystal_layout.size_of_int(64), crystal_layout.align_of_int(64)
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.alignment.should eq llvm_layout.abi_alignment(ctx.struct([
ctx.int8,
ctx.int32,
ctx.float,
ctx.int16,
ctx.int64,
ctx.int8,
] of LLVM::Type)) * 8
end
end
it "packed struct" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate(packed: true)
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
agg.alignment.should eq llvm_layout.abi_alignment(ctx.struct([
ctx.int8,
ctx.int32,
ctx.int16,
] of LLVM::Type, packed: true)) * 8
end
end
end
end
describe "size_of" do
it "integers" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
[1, 7, 8, 15, 16, 24, 32, 64, 65, 128, 256].each do |n|
crystal_layout.size_of_int(n).should eq llvm_layout.abi_size(ctx.int(n)) * 8
end
end
end
it "floats" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
crystal_layout.size_of_float(16).should eq llvm_layout.abi_size(ctx.half) * 8
crystal_layout.size_of_float(32).should eq llvm_layout.abi_size(ctx.float) * 8
crystal_layout.size_of_float(64).should eq llvm_layout.abi_size(ctx.double) * 8
{% if triple.starts_with?("x86_64") || triple =~ /^i[3456]?86/ %}
crystal_layout.size_of_float(80).should eq llvm_layout.abi_size(ctx.x86_fp80) * 8
{% end %}
crystal_layout.size_of_float(128).should eq llvm_layout.abi_size(ctx.fp128) * 8
end
end
describe "pointer" do
it "default address space (0)" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
crystal_layout.size_of_pointer.should eq llvm_layout.abi_size(ctx.void_pointer) * 8
end
end
it "specific address spaces" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
crystal_layout.pointer_alignments.each do |x|
align = crystal_layout.size_of_pointer(x[:address_space])
align.should eq llvm_layout.abi_size(ctx.void_pointer(x[:address_space])) * 8
end
end
end
end
describe "aggregate" do
it "empty struct" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.size.should eq llvm_layout.abi_size(ctx.struct([] of LLVM::Type)) * 8
end
end
it "one property" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.size.should eq llvm_layout.abi_size(ctx.struct([ctx.int32] of LLVM::Type)) * 8
end
end
it "a few properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
agg.size.should eq llvm_layout.abi_size(ctx.struct([
ctx.int8,
ctx.int32,
ctx.int16,
] of LLVM::Type)) * 8
end
end
it "large properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(512), crystal_layout.align_of_int(512)
agg.size.should eq llvm_layout.abi_size(ctx.struct([
ctx.int8,
ctx.int(512),
] of LLVM::Type)) * 8
end
end
it "many properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_float(32),crystal_layout.align_of_float(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
agg.add crystal_layout.size_of_int(64), crystal_layout.align_of_int(64)
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.size.should eq llvm_layout.abi_size(ctx.struct([
ctx.int8,
ctx.int32,
ctx.float,
ctx.int16,
ctx.int64,
ctx.int8,
] of LLVM::Type)) * 8
end
end
it "packed struct" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate(packed: true)
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
agg.size.should eq llvm_layout.abi_size(ctx.struct([
ctx.int8,
ctx.int32,
ctx.int16,
] of LLVM::Type, packed: true)) * 8
end
end
end
end
describe "offset_of" do
it "empty struct" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
expect_raises(IndexError) { agg.offset(0) }
end
end
it "one property" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
ty = ctx.struct([
ctx.int32,
] of LLVM::Type)
agg.offset(0).should eq llvm_layout.offset_of_element(ty, 0) * 8
end
end
it "a few properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
ty = ctx.struct([
ctx.int8,
ctx.int32,
ctx.int16,
] of LLVM::Type)
3_u32.times { |i| agg.offset(i).should eq llvm_layout.offset_of_element(ty, i) * 8 }
end
end
it "large properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(512), crystal_layout.align_of_int(512)
ty = ctx.struct([
ctx.int8,
ctx.int(512),
] of LLVM::Type)
2_u32.times { |i| agg.offset(i).should eq llvm_layout.offset_of_element(ty, i) * 8 }
end
end
it "many properties" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_float(32),crystal_layout.align_of_float(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
agg.add crystal_layout.size_of_int(64), crystal_layout.align_of_int(64)
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
ty = ctx.struct([
ctx.int8,
ctx.int32,
ctx.float,
ctx.int16,
ctx.int64,
ctx.int8,
] of LLVM::Type)
6_u32.times { |i| agg.offset(i).should eq llvm_layout.offset_of_element(ty, i) * 8 }
end
end
it "packed struct" do
machine_for({{triple}}) do |ctx, llvm_layout, crystal_layout|
agg = crystal_layout.aggregate(packed: true)
agg.add crystal_layout.size_of_int(8), crystal_layout.align_of_int(8)
agg.add crystal_layout.size_of_int(32), crystal_layout.align_of_int(32)
agg.add crystal_layout.size_of_int(16), crystal_layout.align_of_int(16)
ty = ctx.struct([
ctx.int8,
ctx.int32,
ctx.int16,
] of LLVM::Type, packed: true)
3_u32.times { |i| agg.offset(i).should eq llvm_layout.offset_of_element(ty, i) * 8 }
end
end
end
end
{% end %}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment