Skip to content

Commit

Permalink
Merge #1071
Browse files Browse the repository at this point in the history
1071: Add support for non-trapping float-to-int conversions. r=nlewycky a=nlewycky

# Description
Rewrites LLVM implementation of non-trapping float-to-int conversions. LLVM's conversion operations produce unspecified output when the input floats are out of range for the conversion.

Add implementation to singlepass for both x86-64 and AArch64.

# Review

- [x] Add a short description of the the change to the CHANGELOG.md file


Co-authored-by: Nick Lewycky <nick@wasmer.io>
  • Loading branch information
bors[bot] and nlewycky authored Dec 21, 2019
2 parents fa29a1b + bba0129 commit 4018494
Show file tree
Hide file tree
Showing 7 changed files with 1,261 additions and 131 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## **[Unreleased]**

- [#1092](https://github.com/wasmerio/wasmer/pull/1092) Add `get_utf8_string_with_nul` to `WasmPtr` to read nul-terminated strings from memory.
- [#1071](https://github.com/wasmerio/wasmer/pull/1071) Add support for non-trapping float-to-int conversions, enabled by default.

## 0.12.0 - 2019-12-18

Expand Down
287 changes: 261 additions & 26 deletions lib/llvm-backend/src/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use inkwell::{
module::{Linkage, Module},
passes::PassManager,
targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine},
types::{BasicType, BasicTypeEnum, FunctionType, PointerType, VectorType},
types::{
BasicType, BasicTypeEnum, FloatMathType, FunctionType, IntType, PointerType, VectorType,
},
values::{
BasicValue, BasicValueEnum, FloatValue, FunctionValue, IntValue, PhiValue, PointerValue,
VectorValue,
Expand Down Expand Up @@ -99,13 +101,12 @@ fn splat_vector<'ctx>(
}

// Convert floating point vector to integer and saturate when out of range.
// TODO: generalize to non-vectors using FloatMathType, IntMathType, etc. for
// https://github.com/WebAssembly/nontrapping-float-to-int-conversions/blob/master/proposals/nontrapping-float-to-int-conversion/Overview.md
fn trunc_sat<'ctx>(
fn trunc_sat<'ctx, T: FloatMathType<'ctx>>(
builder: &Builder<'ctx>,
intrinsics: &Intrinsics<'ctx>,
fvec_ty: VectorType<'ctx>,
ivec_ty: VectorType<'ctx>,
fvec_ty: T,
ivec_ty: T::MathConvType,
lower_bound: u64, // Exclusive (lowest representable value)
upper_bound: u64, // Exclusive (greatest representable value)
int_min_value: u64,
Expand All @@ -126,8 +127,12 @@ fn trunc_sat<'ctx>(
// f) Use our previous comparison results to replace certain zeros with
// int_min or int_max.

let is_signed = int_min_value != 0;
let fvec_ty = fvec_ty.as_basic_type_enum().into_vector_type();
let ivec_ty = ivec_ty.as_basic_type_enum().into_vector_type();
let fvec_element_ty = fvec_ty.get_element_type().into_float_type();
let ivec_element_ty = ivec_ty.get_element_type().into_int_type();

let is_signed = int_min_value != 0;
let int_min_value = splat_vector(
builder,
intrinsics,
Expand All @@ -149,26 +154,26 @@ fn trunc_sat<'ctx>(
let lower_bound = if is_signed {
builder.build_signed_int_to_float(
ivec_element_ty.const_int(lower_bound, is_signed),
fvec_ty.get_element_type().into_float_type(),
fvec_element_ty,
"",
)
} else {
builder.build_unsigned_int_to_float(
ivec_element_ty.const_int(lower_bound, is_signed),
fvec_ty.get_element_type().into_float_type(),
fvec_element_ty,
"",
)
};
let upper_bound = if is_signed {
builder.build_signed_int_to_float(
ivec_element_ty.const_int(upper_bound, is_signed),
fvec_ty.get_element_type().into_float_type(),
fvec_element_ty,
"",
)
} else {
builder.build_unsigned_int_to_float(
ivec_element_ty.const_int(upper_bound, is_signed),
fvec_ty.get_element_type().into_float_type(),
fvec_element_ty,
"",
)
};
Expand Down Expand Up @@ -220,6 +225,93 @@ fn trunc_sat<'ctx>(
.into_int_value()
}

// Convert floating point vector to integer and saturate when out of range.
// https://github.com/WebAssembly/nontrapping-float-to-int-conversions/blob/master/proposals/nontrapping-float-to-int-conversion/Overview.md
fn trunc_sat_scalar<'ctx>(
builder: &Builder<'ctx>,
int_ty: IntType<'ctx>,
lower_bound: u64, // Exclusive (lowest representable value)
upper_bound: u64, // Exclusive (greatest representable value)
int_min_value: u64,
int_max_value: u64,
value: FloatValue<'ctx>,
name: &str,
) -> IntValue<'ctx> {
// TODO: this is a scalarized version of the process in trunc_sat. Either
// we should merge with trunc_sat, or we should simplify this function.

// a) Compare value with itself to identify NaN.
// b) Compare value inttofp(upper_bound) to identify values that need to
// saturate to max.
// c) Compare value with inttofp(lower_bound) to identify values that need
// to saturate to min.
// d) Use select to pick from either zero or the input vector depending on
// whether the comparison indicates that we have an unrepresentable
// value.
// e) Now that the value is safe, fpto[su]i it.
// f) Use our previous comparison results to replace certain zeros with
// int_min or int_max.

let is_signed = int_min_value != 0;
let int_min_value = int_ty.const_int(int_min_value, is_signed);
let int_max_value = int_ty.const_int(int_max_value, is_signed);

let lower_bound = if is_signed {
builder.build_signed_int_to_float(
int_ty.const_int(lower_bound, is_signed),
value.get_type(),
"",
)
} else {
builder.build_unsigned_int_to_float(
int_ty.const_int(lower_bound, is_signed),
value.get_type(),
"",
)
};
let upper_bound = if is_signed {
builder.build_signed_int_to_float(
int_ty.const_int(upper_bound, is_signed),
value.get_type(),
"",
)
} else {
builder.build_unsigned_int_to_float(
int_ty.const_int(upper_bound, is_signed),
value.get_type(),
"",
)
};

let zero = value.get_type().const_zero();

let nan_cmp = builder.build_float_compare(FloatPredicate::UNO, value, zero, "nan");
let above_upper_bound_cmp =
builder.build_float_compare(FloatPredicate::OGT, value, upper_bound, "above_upper_bound");
let below_lower_bound_cmp =
builder.build_float_compare(FloatPredicate::OLT, value, lower_bound, "below_lower_bound");
let not_representable = builder.build_or(
builder.build_or(nan_cmp, above_upper_bound_cmp, ""),
below_lower_bound_cmp,
"not_representable_as_int",
);
let value = builder
.build_select(not_representable, zero, value, "safe_to_convert")
.into_float_value();
let value = if is_signed {
builder.build_float_to_signed_int(value, int_ty, "as_int")
} else {
builder.build_float_to_unsigned_int(value, int_ty, "as_int")
};
let value = builder
.build_select(above_upper_bound_cmp, int_max_value, value, "")
.into_int_value();
let value = builder
.build_select(below_lower_bound_cmp, int_min_value, value, name)
.into_int_value();
builder.build_bitcast(value, int_ty, "").into_int_value()
}

fn trap_if_not_representable_as_int<'ctx>(
builder: &Builder<'ctx>,
intrinsics: &Intrinsics<'ctx>,
Expand Down Expand Up @@ -4457,10 +4549,36 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
builder.build_float_to_signed_int(v1, intrinsics.i32_ty, &state.var_name());
state.push1(res);
}
Operator::I32TruncSSatF32 | Operator::I32TruncSSatF64 => {
let v1 = state.pop1()?.into_float_value();
let res =
builder.build_float_to_signed_int(v1, intrinsics.i32_ty, &state.var_name());
Operator::I32TruncSSatF32 => {
let (v, i) = state.pop1_extra()?;
let v = apply_pending_canonicalization(builder, intrinsics, v, i);
let v = v.into_float_value();
let res = trunc_sat_scalar(
builder,
intrinsics.i32_ty,
LEF32_GEQ_I32_MIN,
GEF32_LEQ_I32_MAX,
std::i32::MIN as u32 as u64,
std::i32::MAX as u32 as u64,
v,
&state.var_name(),
);
state.push1(res);
}
Operator::I32TruncSSatF64 => {
let (v, i) = state.pop1_extra()?;
let v = apply_pending_canonicalization(builder, intrinsics, v, i);
let v = v.into_float_value();
let res = trunc_sat_scalar(
builder,
intrinsics.i32_ty,
LEF64_GEQ_I32_MIN,
GEF64_LEQ_I32_MAX,
std::i32::MIN as u64,
std::i32::MAX as u64,
v,
&state.var_name(),
);
state.push1(res);
}
Operator::I64TruncSF32 => {
Expand Down Expand Up @@ -4490,10 +4608,36 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
builder.build_float_to_signed_int(v1, intrinsics.i64_ty, &state.var_name());
state.push1(res);
}
Operator::I64TruncSSatF32 | Operator::I64TruncSSatF64 => {
let v1 = state.pop1()?.into_float_value();
let res =
builder.build_float_to_signed_int(v1, intrinsics.i64_ty, &state.var_name());
Operator::I64TruncSSatF32 => {
let (v, i) = state.pop1_extra()?;
let v = apply_pending_canonicalization(builder, intrinsics, v, i);
let v = v.into_float_value();
let res = trunc_sat_scalar(
builder,
intrinsics.i64_ty,
LEF32_GEQ_I64_MIN,
GEF32_LEQ_I64_MAX,
std::i64::MIN as u64,
std::i64::MAX as u64,
v,
&state.var_name(),
);
state.push1(res);
}
Operator::I64TruncSSatF64 => {
let (v, i) = state.pop1_extra()?;
let v = apply_pending_canonicalization(builder, intrinsics, v, i);
let v = v.into_float_value();
let res = trunc_sat_scalar(
builder,
intrinsics.i64_ty,
LEF64_GEQ_I64_MIN,
GEF64_LEQ_I64_MAX,
std::i64::MIN as u64,
std::i64::MAX as u64,
v,
&state.var_name(),
);
state.push1(res);
}
Operator::I32TruncUF32 => {
Expand Down Expand Up @@ -4522,10 +4666,36 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
builder.build_float_to_unsigned_int(v1, intrinsics.i32_ty, &state.var_name());
state.push1(res);
}
Operator::I32TruncUSatF32 | Operator::I32TruncUSatF64 => {
let v1 = state.pop1()?.into_float_value();
let res =
builder.build_float_to_unsigned_int(v1, intrinsics.i32_ty, &state.var_name());
Operator::I32TruncUSatF32 => {
let (v, i) = state.pop1_extra()?;
let v = apply_pending_canonicalization(builder, intrinsics, v, i);
let v = v.into_float_value();
let res = trunc_sat_scalar(
builder,
intrinsics.i32_ty,
LEF32_GEQ_U32_MIN,
GEF32_LEQ_U32_MAX,
std::u32::MIN as u64,
std::u32::MAX as u64,
v,
&state.var_name(),
);
state.push1(res);
}
Operator::I32TruncUSatF64 => {
let (v, i) = state.pop1_extra()?;
let v = apply_pending_canonicalization(builder, intrinsics, v, i);
let v = v.into_float_value();
let res = trunc_sat_scalar(
builder,
intrinsics.i32_ty,
LEF64_GEQ_U32_MIN,
GEF64_LEQ_U32_MAX,
std::u32::MIN as u64,
std::u32::MAX as u64,
v,
&state.var_name(),
);
state.push1(res);
}
Operator::I64TruncUF32 => {
Expand Down Expand Up @@ -4554,10 +4724,36 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
builder.build_float_to_unsigned_int(v1, intrinsics.i64_ty, &state.var_name());
state.push1(res);
}
Operator::I64TruncUSatF32 | Operator::I64TruncUSatF64 => {
let v1 = state.pop1()?.into_float_value();
let res =
builder.build_float_to_unsigned_int(v1, intrinsics.i64_ty, &state.var_name());
Operator::I64TruncUSatF32 => {
let (v, i) = state.pop1_extra()?;
let v = apply_pending_canonicalization(builder, intrinsics, v, i);
let v = v.into_float_value();
let res = trunc_sat_scalar(
builder,
intrinsics.i64_ty,
LEF32_GEQ_U64_MIN,
GEF32_LEQ_U64_MAX,
std::u64::MIN,
std::u64::MAX,
v,
&state.var_name(),
);
state.push1(res);
}
Operator::I64TruncUSatF64 => {
let (v, i) = state.pop1_extra()?;
let v = apply_pending_canonicalization(builder, intrinsics, v, i);
let v = v.into_float_value();
let res = trunc_sat_scalar(
builder,
intrinsics.i64_ty,
LEF64_GEQ_U64_MIN,
GEF64_LEQ_U64_MAX,
std::u64::MIN,
std::u64::MAX,
v,
&state.var_name(),
);
state.push1(res);
}
Operator::F32DemoteF64 => {
Expand Down Expand Up @@ -8791,3 +8987,42 @@ fn is_f64_arithmetic(bits: u64) -> bool {
let bits = bits & 0x7FFF_FFFF_FFFF_FFFF;
bits < 0x7FF8_0000_0000_0000
}

// Constants for the bounds of truncation operations. These are the least or
// greatest exact floats in either f32 or f64 representation
// greater-than-or-equal-to (for least) or less-than-or-equal-to (for greatest)
// the i32 or i64 or u32 or u64 min (for least) or max (for greatest), when
// rounding towards zero.

/// Least Exact Float (32 bits) greater-than-or-equal-to i32::MIN when rounding towards zero.
const LEF32_GEQ_I32_MIN: u64 = std::i32::MIN as u64;
/// Greatest Exact Float (32 bits) less-than-or-equal-to i32::MAX when rounding towards zero.
const GEF32_LEQ_I32_MAX: u64 = 2147483520; // bits as f32: 0x4eff_ffff
/// Least Exact Float (64 bits) greater-than-or-equal-to i32::MIN when rounding towards zero.
const LEF64_GEQ_I32_MIN: u64 = std::i32::MIN as u64;
/// Greatest Exact Float (64 bits) less-than-or-equal-to i32::MAX when rounding towards zero.
const GEF64_LEQ_I32_MAX: u64 = std::i32::MAX as u64;
/// Least Exact Float (32 bits) greater-than-or-equal-to u32::MIN when rounding towards zero.
const LEF32_GEQ_U32_MIN: u64 = std::u32::MIN as u64;
/// Greatest Exact Float (32 bits) less-than-or-equal-to u32::MAX when rounding towards zero.
const GEF32_LEQ_U32_MAX: u64 = 4294967040; // bits as f32: 0x4f7f_ffff
/// Least Exact Float (64 bits) greater-than-or-equal-to u32::MIN when rounding towards zero.
const LEF64_GEQ_U32_MIN: u64 = std::u32::MIN as u64;
/// Greatest Exact Float (64 bits) less-than-or-equal-to u32::MAX when rounding towards zero.
const GEF64_LEQ_U32_MAX: u64 = 4294967295; // bits as f64: 0x41ef_ffff_ffff_ffff
/// Least Exact Float (32 bits) greater-than-or-equal-to i64::MIN when rounding towards zero.
const LEF32_GEQ_I64_MIN: u64 = std::i64::MIN as u64;
/// Greatest Exact Float (32 bits) less-than-or-equal-to i64::MAX when rounding towards zero.
const GEF32_LEQ_I64_MAX: u64 = 9223371487098961920; // bits as f32: 0x5eff_ffff
/// Least Exact Float (64 bits) greater-than-or-equal-to i64::MIN when rounding towards zero.
const LEF64_GEQ_I64_MIN: u64 = std::i64::MIN as u64;
/// Greatest Exact Float (64 bits) less-than-or-equal-to i64::MAX when rounding towards zero.
const GEF64_LEQ_I64_MAX: u64 = 9223372036854774784; // bits as f64: 0x43df_ffff_ffff_ffff
/// Least Exact Float (32 bits) greater-than-or-equal-to u64::MIN when rounding towards zero.
const LEF32_GEQ_U64_MIN: u64 = std::u64::MIN;
/// Greatest Exact Float (32 bits) less-than-or-equal-to u64::MAX when rounding towards zero.
const GEF32_LEQ_U64_MAX: u64 = 18446742974197923840; // bits as f32: 0x5f7f_ffff
/// Least Exact Float (64 bits) greater-than-or-equal-to u64::MIN when rounding towards zero.
const LEF64_GEQ_U64_MIN: u64 = std::u64::MIN;
/// Greatest Exact Float (64 bits) less-than-or-equal-to u64::MAX when rounding towards zero.
const GEF64_LEQ_U64_MAX: u64 = 18446744073709549568; // bits as f64: 0x43ef_ffff_ffff_ffff
Loading

0 comments on commit 4018494

Please sign in to comment.