-
Notifications
You must be signed in to change notification settings - Fork 7
ir: preserve floating-point constant bit patterns #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,16 +14,50 @@ | |
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/Config/llvm-config.h" | ||
| #include "llvm/IR/Attributes.h" | ||
| #include "llvm/IR/Constants.h" | ||
| #include "llvm/IR/DebugLoc.h" | ||
| #include "llvm/IR/DebugInfoMetadata.h" | ||
| #include "llvm/IR/Function.h" | ||
| #include "llvm/IR/IRBuilder.h" | ||
| #include "llvm/IR/Intrinsics.h" | ||
| #include "llvm/IR/LLVMContext.h" | ||
| #include "llvm/IR/Module.h" | ||
| #include <algorithm> | ||
|
|
||
| using namespace llvm; | ||
|
|
||
| LLVMValueRef LLVMGoConstFPFromBits(LLVMTypeRef Ty, const uint64_t *Words, | ||
| unsigned NumWords) { | ||
| if (!Ty || !unwrap(Ty)->isFloatingPointTy()) | ||
| return nullptr; | ||
| auto *T = unwrap(Ty); | ||
| unsigned BitWidth = T->getScalarSizeInBits(); | ||
| if (NumWords != (BitWidth + 63) / 64 || !Words) | ||
| return nullptr; | ||
| if (BitWidth % 64 && (Words[NumWords - 1] >> (BitWidth % 64))) | ||
| return nullptr; | ||
| #if LLVM_VERSION_MAJOR >= 22 | ||
| return LLVMConstFPFromBits(Ty, Words); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| #else | ||
| return wrap(ConstantFP::get( | ||
| T->getContext(), APFloat(T->getFltSemantics(), | ||
| APInt(BitWidth, ArrayRef<uint64_t>(Words, NumWords))))); | ||
| #endif | ||
| } | ||
|
|
||
| unsigned LLVMGoConstFPGetBits(LLVMValueRef Val, uint64_t *Words) { | ||
| if (!Val) | ||
| return 0; | ||
| auto *FP = dyn_cast<ConstantFP>(unwrap(Val)); | ||
| if (!FP) | ||
| return 0; | ||
| APInt Bits = FP->getValueAPF().bitcastToAPInt(); | ||
| unsigned NumWords = Bits.getNumWords(); | ||
| if (Words) | ||
| std::copy_n(Bits.getRawData(), NumWords, Words); | ||
| return NumWords; | ||
| } | ||
|
|
||
| LLVMAttributeRef LLVMGoCreateConstantRangeAttribute( | ||
| LLVMContextRef C, unsigned KindID, unsigned NumBits, | ||
| const uint64_t *LowerWords, const uint64_t *UpperWords) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
|
|
||
| package llvm | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "reflect" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestFloatBits(t *testing.T) { | ||
| // Parse independently specified IR, then rebuild in another context. Values | ||
| // include precision below float64's significand and NaN payload/signaling bits. | ||
| cases := []struct { | ||
| name, ir string | ||
| words []uint64 | ||
| }{ | ||
| {"half", "half 0xH8000", []uint64{0x8000}}, | ||
| {"bfloat", "bfloat 0xR7FC1", []uint64{0x7fc1}}, | ||
| {"float_nan", "float 0x7FF82468A0000000", []uint64{0x7fc12345}}, | ||
| {"double_negative_zero", "double 0x8000000000000000", []uint64{0x8000000000000000}}, | ||
| {"double_snan", "double 0x7FF0000000000001", []uint64{0x7ff0000000000001}}, | ||
| {"double_infinity", "double 0x7FF0000000000000", []uint64{0x7ff0000000000000}}, | ||
| {"double_subnormal", "double 0x0000000000000001", []uint64{1}}, | ||
| {"fp80_precision", "x86_fp80 0xK3FFF8000000000000001", []uint64{0x8000000000000001, 0x3fff}}, | ||
| {"fp80_nan", "x86_fp80 0xK7FFFC000000000000123", []uint64{0xc000000000000123, 0x7fff}}, | ||
| {"fp128_precision", "fp128 0xL00000000000000013FFF000000000000", []uint64{1, 0x3fff000000000000}}, | ||
| {"fp128_nan", "fp128 0xL00000000000001237FFF800000000000", []uint64{0x123, 0x7fff800000000000}}, | ||
| {"ppc_fp128_low_only", "ppc_fp128 0xM00000000000000003FF0000000000000", []uint64{0, 0x3ff0000000000000}}, | ||
| {"ppc_fp128_negative_zero", "ppc_fp128 0xM80000000000000000000000000000000", []uint64{0x8000000000000000, 0}}, | ||
| {"ppc_fp128_noncanonical", "ppc_fp128 0xM3FF00000000000003FF0000000000000", []uint64{0x3ff0000000000000, 0x3ff0000000000000}}, | ||
| {"ppc_fp128_nan", "ppc_fp128 0xM7FF80000000001230000000000000000", []uint64{0x7ff8000000000123, 0}}, | ||
| {"ppc_fp128_precision", "ppc_fp128 0xM3FF00000000000003C90000000000000", []uint64{0x3ff0000000000000, 0x3c90000000000000}}, | ||
| } | ||
| for _, tc := range cases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| srcCtx, dstCtx := NewContext(), NewContext() | ||
| defer dstCtx.Dispose() | ||
| path := filepath.Join(t.TempDir(), "float.ll") | ||
| if err := os.WriteFile(path, []byte("@value = constant "+tc.ir+"\n"), 0600); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| buf, err := NewMemoryBufferFromFile(path) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| src, err := srcCtx.ParseIR(buf) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| value := src.NamedGlobal("value").Initializer() | ||
| words := value.FloatBits() | ||
| if !reflect.DeepEqual(words, tc.words) { | ||
| t.Fatalf("bits = %x, want %x", words, tc.words) | ||
| } | ||
| // Reparse just to obtain the identical destination-context type. | ||
| buf, err = NewMemoryBufferFromFile(path) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| dst, err := dstCtx.ParseIR(buf) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| defer dst.Dispose() | ||
| expected := dst.NamedGlobal("value").Initializer() | ||
| cloned := ConstFloatFromBits(expected.Type(), words) | ||
| if cloned != expected { | ||
| t.Fatalf("rebuilt %s, want %s", cloned, expected) | ||
| } | ||
| src.Dispose() | ||
| srcCtx.Dispose() | ||
| if got := cloned.FloatBits(); !reflect.DeepEqual(got, tc.words) { | ||
| t.Fatalf("after source disposal: %x", got) | ||
| } | ||
| words[0] ^= 1 | ||
| if got := cloned.FloatBits(); !reflect.DeepEqual(got, tc.words) { | ||
| t.Fatalf("returned slice aliases constant: %x", got) | ||
| } | ||
| if err := VerifyModule(dst, ReturnStatusAction); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestFloatBitsInvalidInput(t *testing.T) { | ||
| ctx := NewContext() | ||
| defer ctx.Dispose() | ||
| for name, call := range map[string]func(){ | ||
| "nil_type": func() { ConstFloatFromBits(Type{}, []uint64{0}) }, | ||
| "integer_type": func() { ConstFloatFromBits(ctx.Int64Type(), []uint64{0}) }, | ||
| "vector_type": func() { ConstFloatFromBits(VectorType(ctx.DoubleType(), 2), []uint64{0, 0}) }, | ||
| "empty": func() { ConstFloatFromBits(ctx.DoubleType(), nil) }, | ||
| "too_few": func() { ConstFloatFromBits(ctx.FP128Type(), []uint64{0}) }, | ||
| "too_many": func() { ConstFloatFromBits(ctx.DoubleType(), []uint64{0, 0}) }, | ||
| "unused_high_bits": func() { ConstFloatFromBits(ctx.FloatType(), []uint64{1 << 32}) }, | ||
| "fp80_unused_high_bits": func() { ConstFloatFromBits(ctx.X86FP80Type(), []uint64{0, 1 << 16}) }, | ||
| "nil_value": func() { Value{}.FloatBits() }, | ||
| "integer_value": func() { ConstInt(ctx.Int64Type(), 0, false).FloatBits() }, | ||
| "undef": func() { Undef(ctx.DoubleType()).FloatBits() }, | ||
| } { | ||
| t.Run(name, func(t *testing.T) { | ||
| defer func() { | ||
| if recover() == nil { | ||
| t.Fatal("expected panic") | ||
| } | ||
| }() | ||
| call() | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -927,6 +927,37 @@ func ConstFloatFromString(t Type, str string) (v Value) { | |
| return | ||
| } | ||
|
|
||
| // ConstFloatFromBits constructs a scalar floating-point constant without rounding | ||
| // through float64. words holds the raw representation, least significant word | ||
| // first, independently of host byte order. Its length must be ceil(bitWidth/64), | ||
| // and unused high bits in the final word must be zero. It panics for an invalid | ||
| // type or representation. For ppc_fp128, the first word is the leading double | ||
| // and the second word is the trailing double, as in LLVM's APFloat representation. | ||
| func ConstFloatFromBits(t Type, words []uint64) (v Value) { | ||
| var data *C.uint64_t | ||
| if len(words) != 0 { | ||
| data = (*C.uint64_t)(unsafe.Pointer(&words[0])) | ||
| } | ||
| v.C = C.LLVMGoConstFPFromBits(t.C, data, C.uint(len(words))) | ||
| if v.IsNil() { | ||
| panic("llvm: invalid floating-point type or bit representation") | ||
| } | ||
| return | ||
| } | ||
|
|
||
| // FloatBits returns a copy of a scalar floating-point constant's raw APFloat | ||
| // representation in least-significant-word-first order. Unused high bits in the | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor doc clarity: unlike |
||
| // final word are zero. It panics if v is not a scalar floating-point constant. | ||
| func (v Value) FloatBits() []uint64 { | ||
| n := C.LLVMGoConstFPGetBits(v.C, nil) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor performance note (not blocking): |
||
| if n == 0 { | ||
| panic("llvm: FloatBits requires a scalar floating-point constant") | ||
| } | ||
| words := make([]uint64, int(n)) | ||
| C.LLVMGoConstFPGetBits(v.C, (*C.uint64_t)(unsafe.Pointer(&words[0]))) | ||
| return words | ||
| } | ||
|
|
||
| func (v Value) ZExtValue() uint64 { return uint64(C.LLVMConstIntGetZExtValue(v.C)) } | ||
| func (v Value) SExtValue() int64 { return int64(C.LLVMConstIntGetSExtValue(v.C)) } | ||
| func (v Value) DoubleValue() (result float64, inexact bool) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The high-bit check correctly guards non-canonical inputs and correctly uses
BitWidth % 64to avoid an undefined full-width shift. It relies ongetScalarSizeInBits()matching the width ofbitcastToAPInt()for every FP semantics (they agree for all current types, including x86_fp80 at 80 bits, which thefp80_unused_high_bitstest guards). A one-line comment noting the word count is derived from the scalar/primitive size (not the in-memory size) would help future readers, given the x86_fp80 80-vs-128-bit distinction.