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

Adding the decimal functions for LLVM #7261

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2c2bcf2
Started work with definitions for decimals
wizard7377 Nov 28, 2024
935ef09
Getting started with casting
wizard7377 Nov 28, 2024
0222264
Merge branch 'roc-lang:main' into dec_funcs
wizard7377 Nov 28, 2024
3b7dd6d
Created the decimal cast, max and min values, for LLVM
wizard7377 Nov 28, 2024
11f36ef
Merge branch 'main' into dec_funcs
wizard7377 Nov 29, 2024
4216179
Started working on checked conversions
wizard7377 Nov 29, 2024
2d03853
Got the fundementals of the overflow working
wizard7377 Nov 30, 2024
137b565
Cleaned up code
wizard7377 Nov 30, 2024
c301dc7
Merge remote-tracking branch 'upstream/main' into dec_funcs
wizard7377 Nov 30, 2024
3217ec3
Merge remote-tracking branch 'refs/remotes/origin/dec_funcs' into dec…
wizard7377 Nov 30, 2024
4c39767
Got integer out of bounds working
wizard7377 Nov 30, 2024
87ca37d
Link
wizard7377 Nov 30, 2024
fc04410
Finished work on tests
wizard7377 Dec 1, 2024
97084f4
Merge branch 'main' into dec_funcs
wizard7377 Dec 1, 2024
8ec16a3
Re-adding Lines
wizard7377 Dec 1, 2024
55bb57d
Fixed mono
wizard7377 Dec 1, 2024
12c2dbd
Cleaned up code
wizard7377 Dec 1, 2024
3d4ab66
Merge branch 'main' into dec_funcs
wizard7377 Dec 2, 2024
739d616
Merge branch 'main' into dec_funcs
wizard7377 Dec 5, 2024
3bde9e4
Merge branch 'main' into dec_funcs
wizard7377 Dec 5, 2024
f3315b1
Merge branch 'main' into dec_funcs
wizard7377 Dec 12, 2024
22b318e
Merge remote-tracking branch 'remote/main' into dec_funcs
lukewilliamboswell Dec 19, 2024
83a3346
print diff for new panics
Anton-4 Dec 20, 2024
8d41986
back to old improve_panics
Anton-4 Dec 23, 2024
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
12 changes: 12 additions & 0 deletions crates/compiler/builtins/roc/Num.roc
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ module [
maxI128,
minU128,
maxU128,
minDec,
maxDec,
minF32,
maxF32,
minF64,
Expand All @@ -145,6 +147,8 @@ module [
toU64Checked,
toU128,
toU128Checked,
toDec,
toDecChecked,
toF32,
toF32Checked,
toF64,
Expand Down Expand Up @@ -1386,6 +1390,12 @@ minU128 = 0
maxU128 : U128
maxU128 = 340282366920938463463374607431768211455

minDec : Dec
minDec = -170141183460469231731.687303715884105728

maxDec : Dec
maxDec = 170141183460469231731.687303715884105727

minF32 : F32
minF32 = -3.40282347e38

Expand All @@ -1411,6 +1421,7 @@ toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128

toDec : Num * -> Dec
## Converts a [Num] to an [F32]. If the given number can't be precisely represented in an [F32],
## the returned number may be different from the given number.
toF32 : Num * -> F32
Expand All @@ -1432,6 +1443,7 @@ toU16Checked : Int * -> Result U16 [OutOfBounds]
toU32Checked : Int * -> Result U32 [OutOfBounds]
toU64Checked : Int * -> Result U64 [OutOfBounds]
toU128Checked : Int * -> Result U128 [OutOfBounds]
toDecChecked : Num * -> Result Dec [OutOfBounds]
toF32Checked : Num * -> Result F32 [OutOfBounds]
toF64Checked : Num * -> Result F64 [OutOfBounds]

Expand Down
7 changes: 5 additions & 2 deletions crates/compiler/can/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_U128 => Some(lowlevel_1(Symbol::NUM_TO_U128, LowLevel::NumIntCast, var_store)),

Symbol::NUM_INT_CAST => Some(lowlevel_1(Symbol::NUM_INT_CAST, LowLevel::NumIntCast, var_store)),

Symbol::NUM_TO_DEC => Some(lowlevel_1(Symbol::NUM_TO_DEC, LowLevel::NumToDecCast, var_store)),
Symbol::NUM_TO_F32 => Some(lowlevel_1(Symbol::NUM_TO_F32, LowLevel::NumToFloatCast, var_store)),
Symbol::NUM_TO_F64 => Some(lowlevel_1(Symbol::NUM_TO_F64, LowLevel::NumToFloatCast, var_store)),

Expand All @@ -50,6 +50,8 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_U64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U64_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_U128_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U128_CHECKED, var_store, LowLevel::NumToIntChecked)),

Symbol::NUM_TO_DEC_CHECKED => Some(to_num_checked(Symbol::NUM_TO_DEC_CHECKED, var_store, LowLevel::NumToDecChecked)),

Symbol::NUM_TO_F32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F32_CHECKED, var_store, LowLevel::NumToFloatChecked)),
Symbol::NUM_TO_F64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F64_CHECKED, var_store, LowLevel::NumToFloatChecked)),

Expand Down Expand Up @@ -79,7 +81,8 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
LowLevel::NumToFloatCast => unreachable!(),
LowLevel::NumToIntChecked => unreachable!(),
LowLevel::NumToFloatChecked => unreachable!(),

LowLevel::NumToDecCast => unreachable!(),
LowLevel::NumToDecChecked => unreachable!(),
// these are used internally and not tied to a symbol
LowLevel::Hash => unimplemented!(),
LowLevel::PtrCast => unimplemented!(),
Expand Down
1 change: 1 addition & 0 deletions crates/compiler/gen_dev/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,7 @@ trait Backend<'a> {

self.build_num_int_cast(sym, &args[0], source_width, target_width)
}

LowLevel::NumIsMultipleOf => {
let int_width = arg_layouts[0].try_int_width().unwrap();
let intrinsic = bitcode::NUM_IS_MULTIPLE_OF[int_width].to_string();
Expand Down
1 change: 1 addition & 0 deletions crates/compiler/gen_llvm/src/llvm/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,7 @@ pub fn module_from_builtins<'ctx>(
"__modti3",
"__muloti4",
"__udivti3",
"__fixdfti",
"__umodti3",
// Roc special functions
"__roc_force_longjmp",
Expand Down
151 changes: 151 additions & 0 deletions crates/compiler/gen_llvm/src/llvm/lowlevel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,135 @@ pub(crate) fn run_low_level<'a, 'ctx>(
let Ok(value) = result else { todo!() };
value.into()
}
NumToDecCast | NumToDecChecked => {
let checked = matches!(op, NumToDecChecked);
const DEC_RIGHT_DIGITS: u64 = 10u64.pow(18);
const MAX_DEC_F: f64 = 170_141_183_460_469_231_731.687_303_715_884_105_727;
const MAX_DEC_IH: u64 = 9;
const MAX_DEC_IL: u64 = 4120486797000000000;
let to = env.context.i128_type();
let float_type = env.context.f64_type();
let llvm_digits = to.const_int(DEC_RIGHT_DIGITS, false);
let llvm_max_dec_f = float_type.const_float(MAX_DEC_F);
let llvm_max_dec_i = to.const_int_arbitrary_precision(&[MAX_DEC_IL, MAX_DEC_IH]);
let llvm_min_dec_i = to
.const_int_arbitrary_precision(&[MAX_DEC_IL, MAX_DEC_IH])
.const_not();
let llvm_divisor = float_type.const_float(DEC_RIGHT_DIGITS as f64);

arguments_with_layouts!((arg, arg_layout));
//Get the actual value that we are converting to
let result = match layout_interner.get_repr(arg_layout) {
//If integer
LayoutRepr::Builtin(Builtin::Int(width)) => {
//Extend to I128
let Ok(converted_value) = (if width.is_signed() {
env.builder
.build_int_s_extend(arg.into_int_value(), to, "inc_cast")
} else {
env.builder
.build_int_z_extend(arg.into_int_value(), to, "inc_cast")
}) else {
todo!()
};
//Add decimal places
let Ok(result) =
env.builder
.build_int_mul(converted_value, llvm_digits, "deci_cast")
else {
todo!()
};
result.as_basic_value_enum()
}
LayoutRepr::Builtin(Builtin::Float(_)) => {
let Ok(float_result) = env.builder.build_float_mul(
arg.into_float_value(),
llvm_divisor,
"decf_cast",
) else {
todo!()
};
let Ok(int_result) =
env.builder
.build_float_to_signed_int(float_result, to, "dec_place")
else {
todo!()
};
int_result.as_basic_value_enum()
}
LayoutRepr::Builtin(Builtin::Decimal) => arg,
_ => {
todo!()
}
};
//Look and see if the conversion is checked or not
if checked {
let bool_false = env.context.bool_type().const_zero();
let with_overflow = match layout_interner.get_repr(arg_layout) {
//If the value being is converted into a integer
LayoutRepr::Builtin(Builtin::Int(width)) => {
//`Dec` can always fit `I64`s and `U64`s and smaller, no need to check at runtime
if width.stack_size() <= 4 {
bool_false
} else if width.is_signed() {
let compare_arg = env
.builder
.build_int_z_extend_or_bit_cast(
arg.into_int_value(),
to,
"int_cast",
)
.unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this unwrap be reasonably avoided so we don't crash the compiler with a bad error message?

//If VA > +Dec
let pos_res = env.builder.new_build_int_compare(
inkwell::IntPredicate::SGT,
compare_arg,
llvm_max_dec_i,
"int_compare",
);
//If VA < -Dec
let neg_res = env.builder.new_build_int_compare(
inkwell::IntPredicate::SGT,
llvm_min_dec_i,
compare_arg,
"int_compare",
);
env.builder.new_build_or(pos_res, neg_res, "or_ops")
} else {
env.builder.new_build_int_compare(
inkwell::IntPredicate::SGT,
arg.into_int_value(),
llvm_max_dec_i,
"int_compare",
)
}
}
LayoutRepr::Builtin(Builtin::Float(_)) => env.builder.new_build_float_compare(
inkwell::FloatPredicate::OGT,
arg.into_float_value(),
llvm_max_dec_f,
"float_compare",
),
//A decimal can obviously fit itself
LayoutRepr::Builtin(Builtin::Decimal) => bool_false,
_ => {
unreachable!()
}
};

let return_int_type: BasicTypeEnum = env.context.i128_type().into();
result_with(
env,
return_int_type,
result,
env.context.bool_type().into(),
with_overflow,
)
.into()
} else {
result
}
}
NumToFloatCast => {
arguments_with_layouts!((arg, arg_layout));

Expand Down Expand Up @@ -2876,3 +3005,25 @@ fn load_symbol_and_lambda_set<'a, 'ctx>(
other => panic!("Not a lambda set: {other:?}, {ptr:?}"),
}
}
use inkwell::types::BasicTypeEnum;
///Change from a basic LLVM value to a Roc `Result`
fn result_with<'ctx>(
env: &Env<'_, 'ctx, '_>,
good_type: BasicTypeEnum,
good_value: impl BasicValue<'ctx>,
bad_type: BasicTypeEnum,
bad_value: impl BasicValue<'ctx>,
) -> StructValue<'ctx> {
let struct_type = env.context.struct_type(&[good_type, bad_type], false);
let return_value = struct_type.const_zero();
let return_value = env
.builder
.build_insert_value(return_value, good_value, 0, "good_value")
.unwrap();
let return_value = env
.builder
.build_insert_value(return_value, bad_value, 1, "is_bad")
.unwrap();

return_value.into_struct_value()
}
Comment on lines +3008 to +3029
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can these unwraps be reasonably avoided so we don't crash the compiler with a bad error message?

6 changes: 6 additions & 0 deletions crates/compiler/gen_wasm/src/low_level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2066,6 +2066,12 @@ impl<'a> LowLevelCall<'a> {
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
}
}
NumToDecCast => {
todo!("Need to implement this");
}
NumToDecChecked => {
todo!("Need to implement this");
}
NumToFloatCast => {
self.load_args(backend);
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]];
Expand Down
5 changes: 4 additions & 1 deletion crates/compiler/module/src/low_level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ pub enum LowLevel {
NumShiftRightZfBy,
NumIntCast,
NumToFloatCast,
NumToDecCast,
NumToIntChecked,
NumToFloatChecked,
NumToDecChecked,
NumToStr,
NumCountLeadingZeroBits,
NumCountTrailingZeroBits,
Expand Down Expand Up @@ -216,7 +218,8 @@ macro_rules! map_symbol_to_lowlevel {
LowLevel::NumToFloatCast => unreachable!(),
LowLevel::NumToIntChecked => unreachable!(),
LowLevel::NumToFloatChecked => unreachable!(),

LowLevel::NumToDecCast => unreachable!(),
LowLevel::NumToDecChecked => unreachable!(),

// these are used internally and not tied to a symbol
LowLevel::Hash => unimplemented!(),
Expand Down
4 changes: 4 additions & 0 deletions crates/compiler/module/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,10 @@ define_builtins! {
166 NUM_NAN_F64: "nanF64"
167 NUM_INFINITY_F32: "infinityF32"
168 NUM_INFINITY_F64: "infinityF64"
169 NUM_TO_DEC: "toDec"
170 NUM_TO_DEC_CHECKED: "toDecChecked"
171 NUM_MAX_DEC: "maxDec"
172 NUM_MIN_DEC: "minDec"
}
4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
Expand Down
2 changes: 2 additions & 0 deletions crates/compiler/mono/src/drop_specialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,8 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
| NumToIntChecked
| NumToFloatCast
| NumToFloatChecked
| NumToDecCast
| NumToDecChecked
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits
Expand Down
2 changes: 2 additions & 0 deletions crates/compiler/mono/src/inc_dec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,8 @@ pub(crate) fn lowlevel_borrow_signature(op: LowLevel) -> &'static [Ownership] {
| NumAsin
| NumIntCast
| NumToIntChecked
| NumToDecCast
| NumToDecChecked
| NumToFloatCast
| NumToFloatChecked
| NumCountLeadingZeroBits
Expand Down
1 change: 1 addition & 0 deletions crates/compiler/mono/src/low_level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ enum FirstOrder {
NumShiftRightZfBy,
NumIntCast,
NumFloatCast,
NumToDecCast,
Eq,
NotEq,
And,
Expand Down
36 changes: 36 additions & 0 deletions crates/compiler/test_gen/src/gen_num.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4116,3 +4116,39 @@ fn cast_signed_unsigned() {
assert_evals_to!(r"Num.toU8 127i8", 127, u8);
assert_evals_to!(r"Num.toI8 127u8", 127, i8);
}

#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn min_max_dec() {
assert_evals_to!(r"Num.minDec", i128::MIN, i128);
assert_evals_to!(r"Num.maxDec", i128::MAX, i128);
}
#[allow(clippy::non_minimal_cfg)]
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn float_to_dec() {
assert_evals_to!(r"Num.toDec 0f64", RocDec::from_str("0").unwrap(), RocDec);
assert_evals_to!(r"Num.toDecChecked 0f64",RocResult::ok(RocDec::from_str("0").unwrap()),RocResult<RocDec,()>);
assert_evals_to!(r"Num.toDecChecked 999999999999999999999999999.999999999f64",RocResult::err(()),RocResult<RocDec,()>);
assert_evals_to!(r"Num.toDecChecked 0f64", RocResult::ok(RocDec::from_str("0").unwrap()), RocResult<RocDec, ()>);
}
#[allow(clippy::non_minimal_cfg)]
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_dec() {
assert_evals_to!(r"Num.toDec 0u64", RocDec::from_str("0").unwrap(), RocDec);
assert_evals_to!(
r"Num.toDec 100u64",
RocDec::from_str("100").unwrap(),
RocDec
);
assert_evals_to!(r"Num.toDec 0i64", RocDec::from_str("0").unwrap(), RocDec);
assert_evals_to!(
r"Num.toDec 100i64",
RocDec::from_str("100").unwrap(),
RocDec
);
assert_evals_to!(r"Num.toDecChecked -100i64",RocResult::ok(RocDec::from_str("-100").unwrap()), RocResult<RocDec, ()>);
assert_evals_to!(r"Num.toDecChecked 100i64",RocResult::ok(RocDec::from_str("100").unwrap()), RocResult<RocDec, ()>);
assert_evals_to!(r"Num.toDecChecked 999999999999999999999999999u128",RocResult::err(()),RocResult<RocDec,()>);
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/compiler/test_mono/generated/binary_tree_fbip.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading