Skip to content
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

Add support for non-trapping float-to-int conversions. #1071

Merged
merged 12 commits into from
Dec 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment in the abstract, I don't expect you to fix this in this PR or necessarily soon, but on first glance, I'd prefer a struct with methods over numbers in a case like this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reviewing the PR I think it's not as bad as I first thought. I think it's probably possible to clean it up a bit though. Low priority all things considered though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the proposed cleanup here. Create a struct with lower/upper/min/max and a single method on it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just being more semantic where possible. It's a minor thing but a number is a very open ended thing. If it was a bunch of 0-sized structs with a method that returned an upper and lower bound, then mix ups would be less likely.

I don't think it's worth the effort here though -- I thought this was more complex than it actually is

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛳️ i32::max_value() doesn't need std:: 🤔 either way is fine though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think std::i32::MIN is shorter than i32::max_value()? I tried i32::MIN and the compiler told me I need a std:: prefix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm fine with either -- the only reason I mention it is that I haven't seen the MIN and MAX constants in a while. Now that I see them again I do remember having seen them in the past.

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