|
| 1 | +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 2 | +// See https://llvm.org/LICENSE.txt for license information. |
| 3 | +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 4 | + |
| 5 | +package llvm |
| 6 | + |
| 7 | +import ( |
| 8 | + "os" |
| 9 | + "path/filepath" |
| 10 | + "reflect" |
| 11 | + "testing" |
| 12 | +) |
| 13 | + |
| 14 | +func TestFloatBits(t *testing.T) { |
| 15 | + // Parse independently specified IR, then rebuild in another context. Values |
| 16 | + // include precision below float64's significand and NaN payload/signaling bits. |
| 17 | + cases := []struct { |
| 18 | + name, ir string |
| 19 | + words []uint64 |
| 20 | + }{ |
| 21 | + {"half", "half 0xH8000", []uint64{0x8000}}, |
| 22 | + {"bfloat", "bfloat 0xR7FC1", []uint64{0x7fc1}}, |
| 23 | + {"float_nan", "float 0x7FF82468A0000000", []uint64{0x7fc12345}}, |
| 24 | + {"double_negative_zero", "double 0x8000000000000000", []uint64{0x8000000000000000}}, |
| 25 | + {"double_snan", "double 0x7FF0000000000001", []uint64{0x7ff0000000000001}}, |
| 26 | + {"double_infinity", "double 0x7FF0000000000000", []uint64{0x7ff0000000000000}}, |
| 27 | + {"double_subnormal", "double 0x0000000000000001", []uint64{1}}, |
| 28 | + {"fp80_precision", "x86_fp80 0xK3FFF8000000000000001", []uint64{0x8000000000000001, 0x3fff}}, |
| 29 | + {"fp80_nan", "x86_fp80 0xK7FFFC000000000000123", []uint64{0xc000000000000123, 0x7fff}}, |
| 30 | + {"fp128_precision", "fp128 0xL00000000000000013FFF000000000000", []uint64{1, 0x3fff000000000000}}, |
| 31 | + {"fp128_nan", "fp128 0xL00000000000001237FFF800000000000", []uint64{0x123, 0x7fff800000000000}}, |
| 32 | + {"ppc_fp128_low_only", "ppc_fp128 0xM00000000000000003FF0000000000000", []uint64{0, 0x3ff0000000000000}}, |
| 33 | + {"ppc_fp128_negative_zero", "ppc_fp128 0xM80000000000000000000000000000000", []uint64{0x8000000000000000, 0}}, |
| 34 | + {"ppc_fp128_noncanonical", "ppc_fp128 0xM3FF00000000000003FF0000000000000", []uint64{0x3ff0000000000000, 0x3ff0000000000000}}, |
| 35 | + {"ppc_fp128_nan", "ppc_fp128 0xM7FF80000000001230000000000000000", []uint64{0x7ff8000000000123, 0}}, |
| 36 | + {"ppc_fp128_precision", "ppc_fp128 0xM3FF00000000000003C90000000000000", []uint64{0x3ff0000000000000, 0x3c90000000000000}}, |
| 37 | + } |
| 38 | + for _, tc := range cases { |
| 39 | + t.Run(tc.name, func(t *testing.T) { |
| 40 | + srcCtx, dstCtx := NewContext(), NewContext() |
| 41 | + defer dstCtx.Dispose() |
| 42 | + path := filepath.Join(t.TempDir(), "float.ll") |
| 43 | + if err := os.WriteFile(path, []byte("@value = constant "+tc.ir+"\n"), 0600); err != nil { |
| 44 | + t.Fatal(err) |
| 45 | + } |
| 46 | + buf, err := NewMemoryBufferFromFile(path) |
| 47 | + if err != nil { |
| 48 | + t.Fatal(err) |
| 49 | + } |
| 50 | + src, err := srcCtx.ParseIR(buf) |
| 51 | + if err != nil { |
| 52 | + t.Fatal(err) |
| 53 | + } |
| 54 | + value := src.NamedGlobal("value").Initializer() |
| 55 | + words := value.FloatBits() |
| 56 | + if !reflect.DeepEqual(words, tc.words) { |
| 57 | + t.Fatalf("bits = %x, want %x", words, tc.words) |
| 58 | + } |
| 59 | + // Reparse just to obtain the identical destination-context type. |
| 60 | + buf, err = NewMemoryBufferFromFile(path) |
| 61 | + if err != nil { |
| 62 | + t.Fatal(err) |
| 63 | + } |
| 64 | + dst, err := dstCtx.ParseIR(buf) |
| 65 | + if err != nil { |
| 66 | + t.Fatal(err) |
| 67 | + } |
| 68 | + defer dst.Dispose() |
| 69 | + expected := dst.NamedGlobal("value").Initializer() |
| 70 | + cloned := ConstFloatFromBits(expected.Type(), words) |
| 71 | + if cloned != expected { |
| 72 | + t.Fatalf("rebuilt %s, want %s", cloned, expected) |
| 73 | + } |
| 74 | + src.Dispose() |
| 75 | + srcCtx.Dispose() |
| 76 | + if got := cloned.FloatBits(); !reflect.DeepEqual(got, tc.words) { |
| 77 | + t.Fatalf("after source disposal: %x", got) |
| 78 | + } |
| 79 | + words[0] ^= 1 |
| 80 | + if got := cloned.FloatBits(); !reflect.DeepEqual(got, tc.words) { |
| 81 | + t.Fatalf("returned slice aliases constant: %x", got) |
| 82 | + } |
| 83 | + if err := VerifyModule(dst, ReturnStatusAction); err != nil { |
| 84 | + t.Fatal(err) |
| 85 | + } |
| 86 | + }) |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +func TestFloatBitsInvalidInput(t *testing.T) { |
| 91 | + ctx := NewContext() |
| 92 | + defer ctx.Dispose() |
| 93 | + for name, call := range map[string]func(){ |
| 94 | + "nil_type": func() { ConstFloatFromBits(Type{}, []uint64{0}) }, |
| 95 | + "integer_type": func() { ConstFloatFromBits(ctx.Int64Type(), []uint64{0}) }, |
| 96 | + "vector_type": func() { ConstFloatFromBits(VectorType(ctx.DoubleType(), 2), []uint64{0, 0}) }, |
| 97 | + "empty": func() { ConstFloatFromBits(ctx.DoubleType(), nil) }, |
| 98 | + "too_few": func() { ConstFloatFromBits(ctx.FP128Type(), []uint64{0}) }, |
| 99 | + "too_many": func() { ConstFloatFromBits(ctx.DoubleType(), []uint64{0, 0}) }, |
| 100 | + "unused_high_bits": func() { ConstFloatFromBits(ctx.FloatType(), []uint64{1 << 32}) }, |
| 101 | + "fp80_unused_high_bits": func() { ConstFloatFromBits(ctx.X86FP80Type(), []uint64{0, 1 << 16}) }, |
| 102 | + "nil_value": func() { Value{}.FloatBits() }, |
| 103 | + "integer_value": func() { ConstInt(ctx.Int64Type(), 0, false).FloatBits() }, |
| 104 | + "undef": func() { Undef(ctx.DoubleType()).FloatBits() }, |
| 105 | + } { |
| 106 | + t.Run(name, func(t *testing.T) { |
| 107 | + defer func() { |
| 108 | + if recover() == nil { |
| 109 | + t.Fatal("expected panic") |
| 110 | + } |
| 111 | + }() |
| 112 | + call() |
| 113 | + }) |
| 114 | + } |
| 115 | +} |
0 commit comments