Skip to content

Commit

Permalink
Merge pull request #6591 from FabHof/dec-float-decoding-builtins
Browse files Browse the repository at this point in the history
Add builtins for dec and float
  • Loading branch information
Anton-4 authored Apr 8, 2024
2 parents ab0d626 + 8d1394e commit 83904ec
Show file tree
Hide file tree
Showing 100 changed files with 900 additions and 477 deletions.
5 changes: 4 additions & 1 deletion crates/compiler/build/src/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,10 @@ pub fn rebuild_host(
&env_home,
host_dest.to_str().unwrap(),
zig_host_src.to_str().unwrap(),
"native",
// This used to be "native" but that caused segfaults that were hard to
// reproduce and investigate.
// For context: github.com/roc-lang/roc/pull/6591#issuecomment-2039808944
"x86_64-native",
opt_level,
shared_lib_path,
builtins_host_tempfile.path(),
Expand Down
8 changes: 8 additions & 0 deletions crates/compiler/builtins/bitcode/src/dec.zig
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ pub const RocDec = extern struct {
return self.num;
}

pub fn fromI128(num: i128) RocDec {
return .{ .num = num };
}

pub fn eq(self: RocDec, other: RocDec) bool {
return self.num == other.num;
}
Expand Down Expand Up @@ -1475,6 +1479,10 @@ pub fn toI128(arg: RocDec) callconv(.C) i128 {
return @call(.always_inline, RocDec.toI128, .{arg});
}

pub fn fromI128(arg: i128) callconv(.C) RocDec {
return @call(.always_inline, RocDec.fromI128, .{arg});
}

pub fn eqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool {
return @call(.always_inline, RocDec.eq, .{ arg1, arg2 });
}
Expand Down
5 changes: 5 additions & 0 deletions crates/compiler/builtins/bitcode/src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ comptime {
exportDecFn(dec.tanC, "tan");
exportDecFn(dec.toF64, "to_f64");
exportDecFn(dec.toI128, "to_i128");
exportDecFn(dec.fromI128, "from_i128");
exportDecFn(dec.toStr, "to_str");

inline for (INTEGERS) |T| {
Expand Down Expand Up @@ -110,6 +111,10 @@ comptime {
exportNumFn(num.lessThanOrEqualU128, "less_than_or_equal.u128");
exportNumFn(num.greaterThanU128, "greater_than.u128");
exportNumFn(num.greaterThanOrEqualU128, "greater_than_or_equal.u128");
exportNumFn(num.f32ToParts, "f32_to_parts");
exportNumFn(num.f64ToParts, "f64_to_parts");
exportNumFn(num.f32FromParts, "f32_from_parts");
exportNumFn(num.f64FromParts, "f64_from_parts");

inline for (INTEGERS, 0..) |T, i| {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
Expand Down
38 changes: 38 additions & 0 deletions crates/compiler/builtins/bitcode/src/num.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ pub fn NumParseResult(comptime T: type) type {
};
}

pub const F32Parts = extern struct {
fraction: u32,
exponent: u8,
sign: bool,
};

pub const F64Parts = extern struct {
fraction: u64,
exponent: u16,
sign: bool,
};

pub const U256 = struct {
hi: u128,
lo: u128,
Expand Down Expand Up @@ -630,3 +642,29 @@ pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void {
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}

pub fn f32ToParts(self: f32) callconv(.C) F32Parts {
const u32Value = @as(u32, @bitCast(self));
return F32Parts{
.fraction = u32Value & 0x7fffff,
.exponent = @truncate(u32Value >> 23 & 0xff),
.sign = u32Value >> 31 & 1 == 1,
};
}

pub fn f64ToParts(self: f64) callconv(.C) F64Parts {
const u64Value = @as(u64, @bitCast(self));
return F64Parts{
.fraction = u64Value & 0xfffffffffffff,
.exponent = @truncate(u64Value >> 52 & 0x7ff),
.sign = u64Value >> 63 & 1 == 1,
};
}

pub fn f32FromParts(parts: F32Parts) callconv(.C) f32 {
return @as(f32, @bitCast(parts.fraction & 0x7fffff | (@as(u32, parts.exponent) << 23) | (@as(u32, @intFromBool(parts.sign)) << 31)));
}

pub fn f64FromParts(parts: F64Parts) callconv(.C) f64 {
return @as(f64, @bitCast(parts.fraction & 0xfffffffffffff | (@as(u64, parts.exponent & 0x7ff) << 52) | (@as(u64, @intFromBool(parts.sign)) << 63)));
}
5 changes: 1 addition & 4 deletions crates/compiler/builtins/roc/Hash.roc
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,9 @@ hashI64 = \hasher, n -> addU64 hasher (Num.toU64 n)
hashI128 : a, I128 -> a where a implements Hasher
hashI128 = \hasher, n -> addU128 hasher (Num.toU128 n)

## LOWLEVEL get the i128 representation of a Dec.
i128OfDec : Dec -> I128

## Adds a single [Dec] to a hasher.
hashDec : a, Dec -> a where a implements Hasher
hashDec = \hasher, n -> hashI128 hasher (i128OfDec n)
hashDec = \hasher, n -> hashI128 hasher (Num.withoutDecimalPoint n)

## Adds a container of [Hash]able elements to a [Hasher] by hashing each element.
## The container is iterated using the walk method passed in.
Expand Down
29 changes: 29 additions & 0 deletions crates/compiler/builtins/roc/Num.roc
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ interface Num
toF32Checked,
toF64,
toF64Checked,
withoutDecimalPoint,
withDecimalPoint,
f32ToParts,
f64ToParts,
f32FromParts,
f64FromParts,
]
imports [
Bool.{ Bool },
Expand Down Expand Up @@ -1406,3 +1412,26 @@ toU64Checked : Int * -> Result U64 [OutOfBounds]
toU128Checked : Int * -> Result U128 [OutOfBounds]
toF32Checked : Num * -> Result F32 [OutOfBounds]
toF64Checked : Num * -> Result F64 [OutOfBounds]

## Turns a [Dec] into its [I128] representation by removing the decimal point.
## This is equivalent to multiplying the [Dec] by 10^18.
withoutDecimalPoint : Dec -> I128

## Turns a [I128] into the coresponding [Dec] by adding the decimal point.
## This is equivalent to dividing the [I128] by 10^18.
withDecimalPoint : I128 -> Dec

## Splits a [F32] into its components according to IEEE 754 standard.
f32ToParts : F32 -> { sign : Bool, exponent : U8, fraction : U32 }

## Splits a [F64] into its components according to IEEE 754 standard.
f64ToParts : F64 -> { sign : Bool, exponent : U16, fraction : U64 }

## Combine parts of a [F32] according to IEEE 754 standard.
## The fraction should not be bigger than 0x007F_FFFF, any bigger value will be truncated.
f32FromParts : { sign : Bool, exponent : U8, fraction : U32 } -> F32

## Combine parts of a [F64] according to IEEE 754 standard.
## The fraction should not be bigger than 0x000F_FFFF_FFFF_FFFF, any bigger value will be truncated.
## The exponent should not be bigger than 0x07FF, any bigger value will be truncated.
f64FromParts : { sign : Bool, exponent : U16, fraction : U64 } -> F64
5 changes: 5 additions & 0 deletions crates/compiler/builtins/src/bitcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName =
pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName =
int_intrinsic!("roc_builtins.num.count_trailing_zero_bits");
pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_one_bits");
pub const NUM_F32_TO_PARTS: &str = "roc_builtins.num.f32_to_parts";
pub const NUM_F64_TO_PARTS: &str = "roc_builtins.num.f64_to_parts";
pub const NUM_F32_FROM_PARTS: &str = "roc_builtins.num.f32_from_parts";
pub const NUM_F64_FROM_PARTS: &str = "roc_builtins.num.f64_from_parts";

pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
Expand Down Expand Up @@ -415,6 +419,7 @@ pub const DEC_SUB_SATURATED: &str = "roc_builtins.dec.sub_saturated";
pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
pub const DEC_TAN: &str = "roc_builtins.dec.tan";
pub const DEC_TO_I128: &str = "roc_builtins.dec.to_i128";
pub const DEC_FROM_I128: &str = "roc_builtins.dec.from_i128";
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";
pub const DEC_ROUND: IntrinsicName = int_intrinsic!("roc_builtins.dec.round");
pub const DEC_FLOOR: IntrinsicName = int_intrinsic!("roc_builtins.dec.floor");
Expand Down
7 changes: 6 additions & 1 deletion crates/compiler/can/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,12 @@ map_symbol_to_lowlevel_and_arity! {
NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1,
NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1,
NumCountOneBits; NUM_COUNT_ONE_BITS; 1,
I128OfDec; I128_OF_DEC; 1,
NumWithoutDecimalPoint; NUM_WITHOUT_DECIMAL_POINT; 1,
NumWithDecimalPoint; NUM_WITH_DECIMAL_POINT; 1,
NumF32ToParts; NUM_F32_TO_PARTS; 1,
NumF64ToParts; NUM_F64_TO_PARTS; 1,
NumF32FromParts; NUM_F32_FROM_PARTS; 1,
NumF64FromParts; NUM_F64_FROM_PARTS; 1,

Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
Expand Down
30 changes: 30 additions & 0 deletions crates/compiler/gen_dev/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2029,6 +2029,36 @@ trait Backend<'a> {
}
}

LowLevel::NumWithoutDecimalPoint => {
let intrinsic = bitcode::DEC_TO_I128.to_string();
self.build_fn_call(sym, intrinsic, args, arg_layouts, ret_layout)
}

LowLevel::NumWithDecimalPoint => {
let intrinsic = bitcode::DEC_FROM_I128.to_string();
self.build_fn_call(sym, intrinsic, args, arg_layouts, ret_layout)
}

LowLevel::NumF32ToParts => {
let intrinsic = bitcode::NUM_F32_TO_PARTS.to_string();
self.build_fn_call(sym, intrinsic, args, arg_layouts, ret_layout)
}

LowLevel::NumF64ToParts => {
let intrinsic = bitcode::NUM_F64_TO_PARTS.to_string();
self.build_fn_call(sym, intrinsic, args, arg_layouts, ret_layout)
}

LowLevel::NumF32FromParts => {
let intrinsic = bitcode::NUM_F32_FROM_PARTS.to_string();
self.build_fn_call(sym, intrinsic, args, arg_layouts, ret_layout)
}

LowLevel::NumF64FromParts => {
let intrinsic = bitcode::NUM_F64_FROM_PARTS.to_string();
self.build_fn_call(sym, intrinsic, args, arg_layouts, ret_layout)
}

x => todo!("low level, {:?}", x),
}
}
Expand Down
85 changes: 85 additions & 0 deletions crates/compiler/gen_llvm/src/llvm/bitcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1168,3 +1168,88 @@ pub(crate) fn call_list_bitcode_fn<'ctx>(
}
}
}

pub(crate) fn call_bitcode_fn_with_record_arg<'ctx>(
env: &Env<'_, 'ctx, '_>,
arg: BasicValueEnum<'ctx>,
fn_name: &str,
) -> BasicValueEnum<'ctx> {
let roc_call_alloca = env
.builder
.new_build_alloca(arg.get_type(), "roc_call_alloca");
env.builder.new_build_store(roc_call_alloca, arg);

let fn_val = env.module.get_function(fn_name).unwrap();

let mut args: Vec<BasicValueEnum<'ctx>> = Vec::with_capacity(fn_val.count_params() as usize);
if fn_val.get_first_param().unwrap().is_pointer_value() {
// call by pointer
let zig_call_alloca = env
.builder
.new_build_alloca(arg.get_type(), "zig_return_alloca");
env.builder.new_build_store(zig_call_alloca, arg);
args.push(zig_call_alloca.into());
} else if fn_val.count_params() == 1 {
//c all single with arg as
let zig_param_type = fn_val.get_params()[0].get_type();
let zig_value = env
.builder
.new_build_load(zig_param_type, roc_call_alloca, "zig_value");
args.push(zig_value);
} else {
// split arg
let zig_params_types: Vec<_> = fn_val.get_param_iter().map(|p| p.get_type()).collect();
let zig_record_type = env.context.struct_type(&zig_params_types, false);
let zig_recode_value = env
.builder
.new_build_load(zig_record_type, roc_call_alloca, "zig_value")
.into_struct_value();
for i in 0..fn_val.count_params() {
let zig_value = env
.builder
.build_extract_value(zig_recode_value, i, "zig_value")
.unwrap();
args.push(zig_value);
}
}
call_bitcode_fn(env, &args, fn_name)
}

pub(crate) fn call_bitcode_fn_returning_record<'ctx>(
env: &Env<'_, 'ctx, '_>,
layout: InLayout<'_>,
layout_interner: &STLayoutInterner<'_>,
bitcode_return_type_name: &str,
arg: BasicValueEnum<'ctx>,
fn_name: &str,
) -> BasicValueEnum<'ctx> {
let zig_return_alloca;
let layout_repr = layout_interner.get_repr(layout);
let fn_val = env.module.get_function(fn_name).unwrap();
if fn_val.get_type().get_return_type().is_none() {
// return by pointer
let bitcode_return_type = env
.module
.get_struct_type(bitcode_return_type_name)
.unwrap();
zig_return_alloca = env
.builder
.new_build_alloca(bitcode_return_type, "zig_return_alloca");
call_void_bitcode_fn(env, &[zig_return_alloca.into(), arg], fn_name);
} else {
// direct return
let zig_result = call_bitcode_fn(env, &[arg], fn_name);
zig_return_alloca = env
.builder
.new_build_alloca(zig_result.get_type(), "zig_return_alloca");
env.builder.new_build_store(zig_return_alloca, zig_result);
}

load_roc_value(
env,
layout_interner,
layout_repr,
zig_return_alloca,
"result",
)
}
48 changes: 42 additions & 6 deletions crates/compiler/gen_llvm/src/llvm/lowlevel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ use roc_target::{PtrWidth, Target};

use crate::llvm::{
bitcode::{
call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_list_bitcode_fn,
call_str_bitcode_fn, call_void_bitcode_fn, pass_list_or_string_to_zig_32bit,
pass_string_to_zig_wasm, BitcodeReturns,
call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_bitcode_fn_returning_record,
call_bitcode_fn_with_record_arg, call_list_bitcode_fn, call_str_bitcode_fn,
call_void_bitcode_fn, pass_list_or_string_to_zig_32bit, pass_string_to_zig_wasm,
BitcodeReturns,
},
build::{
cast_basic_basic, complex_bitcast_check_size, create_entry_block_alloca,
Expand Down Expand Up @@ -1172,9 +1173,44 @@ pub(crate) fn run_low_level<'a, 'ctx>(
// which could be useful to look at when implementing this.
todo!("implement checked float conversion");
}
I128OfDec => {
arguments!(dec);
dec_unary_op(env, bitcode::DEC_TO_I128, dec)
NumWithoutDecimalPoint | NumWithDecimalPoint => {
// Dec uses an I128 under the hood, so no conversion is needed.
arguments!(arg);
arg
}
NumF32ToParts => {
arguments!(arg);
let fn_name = bitcode::NUM_F32_TO_PARTS;
call_bitcode_fn_returning_record(
env,
layout,
layout_interner,
"num.F32Parts",
arg,
fn_name,
)
}
NumF64ToParts => {
arguments!(arg);
let fn_name = bitcode::NUM_F64_TO_PARTS;
call_bitcode_fn_returning_record(
env,
layout,
layout_interner,
"num.F64Parts",
arg,
fn_name,
)
}
NumF32FromParts => {
arguments!(arg);
let fn_name = bitcode::NUM_F32_FROM_PARTS;
call_bitcode_fn_with_record_arg(env, arg, fn_name)
}
NumF64FromParts => {
arguments!(arg);
let fn_name = bitcode::NUM_F64_FROM_PARTS;
call_bitcode_fn_with_record_arg(env, arg, fn_name)
}
Eq => {
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
Expand Down
7 changes: 6 additions & 1 deletion crates/compiler/gen_wasm/src/low_level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2017,7 +2017,12 @@ impl<'a> LowLevelCall<'a> {
NumToFloatChecked => {
todo!("implement toF32Checked and toF64Checked");
}
I128OfDec => self.load_args_and_call_zig(backend, bitcode::DEC_TO_I128),
NumWithoutDecimalPoint => self.load_args_and_call_zig(backend, bitcode::DEC_TO_I128),
NumWithDecimalPoint => self.load_args_and_call_zig(backend, bitcode::DEC_FROM_I128),
NumF32ToParts => self.load_args_and_call_zig(backend, bitcode::NUM_F32_TO_PARTS),
NumF64ToParts => self.load_args_and_call_zig(backend, bitcode::NUM_F64_TO_PARTS),
NumF32FromParts => self.load_args_and_call_zig(backend, bitcode::NUM_F32_FROM_PARTS),
NumF64FromParts => self.load_args_and_call_zig(backend, bitcode::NUM_F64_FROM_PARTS),
And => {
self.load_args(backend);
backend.code_builder.i32_and();
Expand Down
Loading

0 comments on commit 83904ec

Please sign in to comment.