From 8b7045a8a7e2c3234ca2e144005427bb416da0d7 Mon Sep 17 00:00:00 2001 From: Joe Eli McIlvain Date: Sat, 24 Aug 2024 22:25:08 -0700 Subject: [PATCH] Add some tests. This commit adds a few reasonable tests (a test for basic program, as well as some tests for `LLVM.Types` and type-related features). It's not full coverage, obviously, but it's better than nothing and we can expand as we add more features and fix more bugs. --- spec/LLVM.Spec.savi | 31 ++++++++- spec/LLVM.Types.Spec.savi | 140 ++++++++++++++++++++++++++++++++++++++ spec/Main.savi | 2 + src/LLVM.Const.savi | 4 +- src/LLVM.Module.savi | 11 +++ src/LLVM.Type.savi | 49 ++++++++++--- src/LLVM.Value.savi | 1 + src/_FFI.savi | 16 ++--- 8 files changed, 234 insertions(+), 20 deletions(-) create mode 100644 spec/LLVM.Types.Spec.savi diff --git a/spec/LLVM.Spec.savi b/spec/LLVM.Spec.savi index 41e79fa..74fbe5d 100644 --- a/spec/LLVM.Spec.savi +++ b/spec/LLVM.Spec.savi @@ -2,5 +2,34 @@ :is Spec :const describes: "LLVM" - :it "has placeholder tests which do nothing" + :it "can build a basic program" llvm = LLVM.new + module = llvm.create_module("hello") + + puts = module.add_function("puts" + llvm.types.function(llvm.types.i32, [llvm.types.ptr]) + ) + hello_world = module.add_global_with_value( + llvm.const.string("Hello, world!") + ).as_value + + main = module.add_function("main" + llvm.types.function(llvm.types.i32) + ) + llvm.build.at(main.add_block("entry")) + llvm.build.call(puts, [hello_world]) + llvm.build.ret(llvm.const.i32(0)) + + assert: "\(module)" == "; ModuleID = 'hello'\n\(<<< + source_filename = "hello" + + @0 = global [14 x i8] c"Hello, world!\00" + + declare i32 @puts(ptr) + + define i32 @main() { + entry: + %0 = call i32 @puts(ptr @0) + ret i32 0 + } + >>>)\n" diff --git a/spec/LLVM.Types.Spec.savi b/spec/LLVM.Types.Spec.savi new file mode 100644 index 0000000..432036c --- /dev/null +++ b/spec/LLVM.Types.Spec.savi @@ -0,0 +1,140 @@ +:class LLVM.Types.Spec + :is Spec + :const describes: "LLVM.Types" + + :it "has various pre-instantiated types, each equatable with itself" + llvm = LLVM.new + + assert: "\(llvm.types.void)" == "void" + assert: "\(llvm.types.ptr)" == "ptr" + assert: "\(llvm.types.i1)" == "i1" + assert: "\(llvm.types.i8)" == "i8" + assert: "\(llvm.types.i16)" == "i16" + assert: "\(llvm.types.i32)" == "i32" + assert: "\(llvm.types.i64)" == "i64" + assert: "\(llvm.types.i128)" == "i128" + assert: "\(llvm.types.f16)" == "half" + assert: "\(llvm.types.bf16)" == "bfloat" + assert: "\(llvm.types.f32)" == "float" + assert: "\(llvm.types.f64)" == "double" + + assert: llvm.types.void == llvm.types.void + assert: llvm.types.ptr == llvm.types.ptr + assert: llvm.types.i1 == llvm.types.i1 + assert: llvm.types.i8 == llvm.types.i8 + assert: llvm.types.i16 == llvm.types.i16 + assert: llvm.types.i32 == llvm.types.i32 + assert: llvm.types.i64 == llvm.types.i64 + assert: llvm.types.i128 == llvm.types.i128 + assert: llvm.types.f16 == llvm.types.f16 + assert: llvm.types.bf16 == llvm.types.bf16 + assert: llvm.types.f32 == llvm.types.f32 + assert: llvm.types.f64 == llvm.types.f64 + + assert: llvm.types.i32 != llvm.types.f32 + + assert: llvm.types.void.kind.is_void + assert: llvm.types.ptr.kind.is_pointer + assert: llvm.types.i1.kind.is_integer + assert: llvm.types.i8.kind.is_integer + assert: llvm.types.i16.kind.is_integer + assert: llvm.types.i32.kind.is_integer + assert: llvm.types.i64.kind.is_integer + assert: llvm.types.i128.kind.is_integer + assert: llvm.types.f16.kind.is_real + assert: llvm.types.bf16.kind.is_real + assert: llvm.types.f32.kind.is_real + assert: llvm.types.f64.kind.is_real + + assert: llvm.types.i1.as_integer_type!.bit_width == 1 + assert: llvm.types.i8.as_integer_type!.bit_width == 8 + assert: llvm.types.i16.as_integer_type!.bit_width == 16 + assert: llvm.types.i32.as_integer_type!.bit_width == 32 + assert: llvm.types.i64.as_integer_type!.bit_width == 64 + assert: llvm.types.i128.as_integer_type!.bit_width == 128 + + :it "can create various composite types, which are structurally equatable" + llvm = LLVM.new + types = llvm.types + void = types.void + i64 = llvm.types.i64 + i32 = llvm.types.i32 + i8 = llvm.types.i8 + + assert: "\(types.array(i64, 10))" == "[10 x i64]" + assert: "\(types.vector(i64, 4))" == "<4 x i64>" + assert: "\(types.literal_struct([i64, i8]))" == "{ i64, i8 }" + assert: "\(types.literal_struct([i32]).set_body([i64, i8]))" == "{ i64, i8 }" + assert: "\(types.function(i32))" == "i32 ()" + assert: "\(types.function(i32, [i64, i8]))" == "i32 (i64, i8)" + assert: "\(types.function(i32, [i64, i8], True))" == "i32 (i64, i8, ...)" + + assert: types.array(i64, 10) == types.array(i64, 10) + assert: types.array(i64, 10) != types.array(i64, 9) + assert: types.vector(i64, 4) == types.vector(i64, 4) + assert: types.vector(i64, 4) != types.vector(i64, 3) + assert: types.literal_struct([i64, i8]) == types.literal_struct([i64, i8]) + assert: types.function(i32, [i64, i8]) == types.function(i32, [i64, i8]) + + a1 = types.array(i64, 10) + a2 = types.array(i32, 9) + + assert: a1.element_type == i64 + assert: a2.element_type == i32 + assert: a1.size == 10 + assert: a2.size == 9 + + fn1 = types.function(i32) + fn2 = types.function(void, [i64, i8]) + fn3 = types.function(i32, [i64, i8], True) + + assert: fn1.return_type == i32 + assert: fn2.return_type == void + assert: fn3.return_type == i32 + assert: fn1.param_types_size == 0 + assert: fn1.param_types.size == 0 + assert: fn2.param_types_size == 2 + assert: fn2.param_types.size == 2 + assert: fn2.param_types[0]! == i64 + assert: fn2.param_types[1]! == i8 + assert: fn1.is_variadic == False + assert: fn2.is_variadic == False + assert: fn3.is_variadic + + :it "can create named structs, which are nominally equatable" + llvm = LLVM.new + + foo = llvm.types.struct("foo") + bar = llvm.types.struct("bar").set_body([llvm.types.i64, llvm.types.i8]) + baz = llvm.types.struct("baz").set_body([llvm.types.i64, llvm.types.i8], True) + + assert: "\(foo)" == "%foo = type opaque" + assert: "\(bar)" == "%bar = type { i64, i8 }" + assert: "\(baz)" == "%baz = type <{ i64, i8 }>" + + assert: foo.element_types_size == 0 + assert: foo.element_types.size == 0 + assert: bar.element_types_size == 2 + assert: bar.element_types.size == 2 + assert: bar.element_types[0]! == llvm.types.i64 + assert: bar.element_types[1]! == llvm.types.i8 + + assert: foo.is_packed == False + assert: bar.is_packed == False + assert: baz.is_packed + + assert: foo.is_opaque + assert: bar.is_opaque == False + assert: baz.is_opaque == False + + assert: foo.is_literal == False + assert: bar.is_literal == False + assert: baz.is_literal == False + assert: llvm.types.literal_struct([llvm.types.i64, llvm.types.i8]).is_literal + + s1 = llvm.types.struct("s").set_body([llvm.types.i64, llvm.types.i8]) + s2 = llvm.types.struct("s").set_body([llvm.types.i64, llvm.types.i8]) + assert: "\(s1)" == "%s = type { i64, i8 }" + assert: "\(s2)" == "%s.0 = type { i64, i8 }" + assert: s1 == s1 + assert: s1 != s2 diff --git a/spec/Main.savi b/spec/Main.savi index bf6a4c6..af82230 100644 --- a/spec/Main.savi +++ b/spec/Main.savi @@ -1,5 +1,7 @@ + :actor Main :new (env Env) Spec.Process.run(env, [ Spec.Run(LLVM.Spec).new(env) + Spec.Run(LLVM.Types.Spec).new(env) ]) diff --git a/src/LLVM.Const.savi b/src/LLVM.Const.savi index 05b33b0..ec3dcd6 100644 --- a/src/LLVM.Const.savi +++ b/src/LLVM.Const.savi @@ -44,13 +44,13 @@ :fun f32(value F32) _FFI.const_real( - _FFI.Cast(LLVM.Type'box, LLVM.Type.FloatingPoint'box).pointer(@_types.f32) + _FFI.Cast(LLVM.Type'box, LLVM.Type.Real'box).pointer(@_types.f32) value.f64 ) :fun f64(value F64) _FFI.const_real( - _FFI.Cast(LLVM.Type'box, LLVM.Type.FloatingPoint'box).pointer(@_types.f64) + _FFI.Cast(LLVM.Type'box, LLVM.Type.Real'box).pointer(@_types.f64) value.f64 ) diff --git a/src/LLVM.Module.savi b/src/LLVM.Module.savi index 952c02b..fbae9d7 100644 --- a/src/LLVM.Module.savi +++ b/src/LLVM.Module.savi @@ -13,6 +13,17 @@ g.initializer = value g + :is IntoString + :fun into_string_space USize + string = _FFI.string(_FFI.print_module_to_string(@_ptr)) + space = string.size + _FFI.dispose_message(string.cpointer) + space + :fun into_string(out String'ref) None + string = _FFI.string(_FFI.print_module_to_string(@_ptr)) + string.into_string(out) + _FFI.dispose_message(string.cpointer) + :fun print_to_file!(filename String) None error = CPointer(U8).null is_error = _FFI.print_module_to_file( diff --git a/src/LLVM.Type.savi b/src/LLVM.Type.savi index 1397da4..04e634b 100644 --- a/src/LLVM.Type.savi +++ b/src/LLVM.Type.savi @@ -23,7 +23,7 @@ :fun is_void: @ == LLVM.Type.Kind.Void :fun is_integer: @ == LLVM.Type.Kind.Integer - :fun is_float: @ == LLVM.Type.Kind.BFloat || ( + :fun is_real: @ == LLVM.Type.Kind.BFloat || ( @ <= LLVM.Type.Kind.PPC_FP128 && @ >= LLVM.Type.Kind.Half ) :fun is_array: @ == LLVM.Type.Kind.Array @@ -38,6 +38,9 @@ :fun kind LLVM.Type.Kind: _FFI.get_type_kind(@) + :is Equatable(@'box) + :fun "=="(other @'box): @_ptr.address == other._ptr.address + :fun into_string_space USize string = _FFI.string(_FFI.print_type_to_string(@)) space = string.size @@ -52,9 +55,9 @@ error! unless @kind.is_integer _FFI.Cast(@, @->(LLVM.Type.Integer)).pointer(@) - :fun as_floating_point_type! @->(LLVM.Type.FloatingPoint) - error! unless @kind.is_floating_point - _FFI.Cast(@, @->(LLVM.Type.FloatingPoint)).pointer(@) + :fun as_real_type! @->(LLVM.Type.Real) + error! unless @kind.is_real + _FFI.Cast(@, @->(LLVM.Type.Real)).pointer(@) :fun as_array_type! @->(LLVM.Type.Array) error! unless @kind.is_array @@ -84,10 +87,13 @@ :fun into_string_space: @as_type.into_string_space :fun into_string(out String'ref): @as_type.into_string(out) + :is Equatable(@'box) + :fun "=="(other @'box): @_ptr.address == other._ptr.address + :: Get the bit width of the integer type. :fun bit_width: _FFI.get_int_type_width(@) -:struct LLVM.Type.FloatingPoint +:struct LLVM.Type.Real :let _ptr CPointer(None) :new _from_ptr(@_ptr) :fun as_type @@ -95,6 +101,9 @@ :fun into_string_space: @as_type.into_string_space :fun into_string(out String'ref): @as_type.into_string(out) + :is Equatable(@'box) + :fun "=="(other @'box): @_ptr.address == other._ptr.address + :struct LLVM.Type.Array :let _ptr CPointer(None) :new _from_ptr(@_ptr) @@ -103,6 +112,9 @@ :fun into_string_space: @as_type.into_string_space :fun into_string(out String'ref): @as_type.into_string(out) + :is Equatable(@'box) + :fun "=="(other @'box): @_ptr.address == other._ptr.address + :: Get the type of elements in the array type. :fun element_type @->(LLVM.Type) _FFI.Cast(LLVM.Type'box, @->(LLVM.Type)).pointer(_FFI.get_array_element_type(@)) @@ -118,6 +130,9 @@ :fun into_string_space: @as_type.into_string_space :fun into_string(out String'ref): @as_type.into_string(out) + :is Equatable(@'box) + :fun "=="(other @'box): @_ptr.address == other._ptr.address + :: Get the type of elements in the vector type. :fun element_type @->(LLVM.Type) _FFI.Cast(LLVM.Type'box, @->(LLVM.Type)).pointer(_FFI.get_vector_element_type(@)) @@ -133,6 +148,9 @@ :fun into_string_space: @as_type.into_string_space :fun into_string(out String'ref): @as_type.into_string(out) + :is Equatable(@'box) + :fun "=="(other @'box): @_ptr.address == other._ptr.address + :: Get the address space of the pointer type. :fun address_space U32: _FFI.get_pointer_address_space(@) @@ -144,6 +162,9 @@ :fun into_string_space: @as_type.into_string_space :fun into_string(out String'ref): @as_type.into_string(out) + :is Equatable(@'box) + :fun "=="(other @'box): @_ptr.address == other._ptr.address + :: Get the name of the struct type. :fun name: _FFI.string(_FFI.get_struct_name(@)) @@ -187,6 +208,9 @@ :fun into_string_space: @as_type.into_string_space :fun into_string(out String'ref): @as_type.into_string(out) + :is Equatable(@'box) + :fun "=="(other @'box): @_ptr.address == other._ptr.address + :: Get the return type of the function type. :fun return_type @->(LLVM.Type) _FFI.Cast(LLVM.Type'box, @->(LLVM.Type)).pointer(_FFI.get_return_type(@)) @@ -238,10 +262,10 @@ @i32 = _FFI.Cast(LLVM.Type.Integer, LLVM.Type).pointer(_FFI.int32_type_in_context(@_ptr)) @i64 = _FFI.Cast(LLVM.Type.Integer, LLVM.Type).pointer(_FFI.int64_type_in_context(@_ptr)) @i128 = _FFI.Cast(LLVM.Type.Integer, LLVM.Type).pointer(_FFI.int128_type_in_context(@_ptr)) - @f16 = _FFI.Cast(LLVM.Type.FloatingPoint, LLVM.Type).pointer(_FFI.half_type_in_context(@_ptr)) - @bf16 = _FFI.Cast(LLVM.Type.FloatingPoint, LLVM.Type).pointer(_FFI.b_float_type_in_context(@_ptr)) - @f32 = _FFI.Cast(LLVM.Type.FloatingPoint, LLVM.Type).pointer(_FFI.float_type_in_context(@_ptr)) - @f64 = _FFI.Cast(LLVM.Type.FloatingPoint, LLVM.Type).pointer(_FFI.double_type_in_context(@_ptr)) + @f16 = _FFI.Cast(LLVM.Type.Real, LLVM.Type).pointer(_FFI.half_type_in_context(@_ptr)) + @bf16 = _FFI.Cast(LLVM.Type.Real, LLVM.Type).pointer(_FFI.b_float_type_in_context(@_ptr)) + @f32 = _FFI.Cast(LLVM.Type.Real, LLVM.Type).pointer(_FFI.float_type_in_context(@_ptr)) + @f64 = _FFI.Cast(LLVM.Type.Real, LLVM.Type).pointer(_FFI.double_type_in_context(@_ptr)) :fun ref array(element_type LLVM.Type'box, size USize) _FFI.array_type2(element_type, size.u64) @@ -264,3 +288,10 @@ :fun ref struct(name String) _FFI.struct_create_named(@_ptr, name.cstring) + :fun ref literal_struct(element_types Array(LLVM.Type'box), packed = False) + _FFI.struct_type_in_context( + @_ptr + element_types.cpointer + element_types.size.u32 + _FFI.Bool.from(packed) + ) diff --git a/src/LLVM.Value.savi b/src/LLVM.Value.savi index e9abd1b..3b4bb33 100644 --- a/src/LLVM.Value.savi +++ b/src/LLVM.Value.savi @@ -41,6 +41,7 @@ cpointer = _FFI.get_value_name_2(@, stack_address_of_variable size) String.val_from_cpointer(cpointer, size, size) + :is IntoString :fun into_string_space USize string = _FFI.string(_FFI.print_value_to_string(@)) space = string.size diff --git a/src/_FFI.savi b/src/_FFI.savi index c3e9c05..a4fa749 100644 --- a/src/_FFI.savi +++ b/src/_FFI.savi @@ -433,31 +433,31 @@ // :: Obtain a 16-bit floating point type from a context. - :ffi half_type_in_context(context _FFI.ContextPtr) LLVM.Type.FloatingPoint + :ffi half_type_in_context(context _FFI.ContextPtr) LLVM.Type.Real :foreign_name LLVMHalfTypeInContext :: Obtain a 16-bit brain floating point type from a context. - :ffi b_float_type_in_context(context _FFI.ContextPtr) LLVM.Type.FloatingPoint + :ffi b_float_type_in_context(context _FFI.ContextPtr) LLVM.Type.Real :foreign_name LLVMBFloatTypeInContext :: Obtain a 32-bit floating point type from a context. - :ffi float_type_in_context(context _FFI.ContextPtr) LLVM.Type.FloatingPoint + :ffi float_type_in_context(context _FFI.ContextPtr) LLVM.Type.Real :foreign_name LLVMFloatTypeInContext :: Obtain a 64-bit floating point type from a context. - :ffi double_type_in_context(context _FFI.ContextPtr) LLVM.Type.FloatingPoint + :ffi double_type_in_context(context _FFI.ContextPtr) LLVM.Type.Real :foreign_name LLVMDoubleTypeInContext :: Obtain a 80-bit floating point type (X87) from a context. - :ffi x86fp80_type_in_context(context _FFI.ContextPtr) LLVM.Type.FloatingPoint + :ffi x86fp80_type_in_context(context _FFI.ContextPtr) LLVM.Type.Real :foreign_name LLVMX86FP80TypeInContext :: Obtain a 128-bit floating point type (112-bit mantissa) from a context. - :ffi fp128_type_in_context(context _FFI.ContextPtr) LLVM.Type.FloatingPoint + :ffi fp128_type_in_context(context _FFI.ContextPtr) LLVM.Type.Real :foreign_name LLVMFP128TypeInContext :: Obtain a 128-bit floating point type (two 64-bits) from a context. - :ffi ppcfp128_type_in_context(context _FFI.ContextPtr) LLVM.Type.FloatingPoint + :ffi ppcfp128_type_in_context(context _FFI.ContextPtr) LLVM.Type.Real :foreign_name LLVMPPCFP128TypeInContext /// @@ -871,7 +871,7 @@ :: Obtain a constant value referring to a double floating point value. :ffi const_real( - real_type LLVM.Type.FloatingPoint'box + real_type LLVM.Type.Real'box value F64 ) LLVM.Value :foreign_name LLVMConstReal