diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 6d56e3b318..2eea0b6464 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -1,4 +1,9 @@ const std = @import("std"); +const math = std.math; +const utils = @import("utils.zig"); + +const ROC_BUILTINS = "roc_builtins"; +const NUM = "num"; // Dec Module const dec = @import("dec.zig"); @@ -72,16 +77,28 @@ comptime { // Num Module const num = @import("num.zig"); + +const INTEGERS = [_]type{ i8, i16, i32, i64, i128, u8, u16, u32, u64, u128 }; +const FLOATS = [_]type{ f32, f64 }; +const NUMBERS = INTEGERS ++ FLOATS; + comptime { - exportNumFn(num.atan, "atan"); - exportNumFn(num.isFinite, "is_finite"); - exportNumFn(num.powInt, "pow_int"); - exportNumFn(num.divCeil, "div_ceil"); - exportNumFn(num.acos, "acos"); - exportNumFn(num.asin, "asin"); exportNumFn(num.bytesToU16C, "bytes_to_u16"); exportNumFn(num.bytesToU32C, "bytes_to_u32"); - exportNumFn(num.round, "round"); + + inline for (INTEGERS) |T| { + num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int."); + num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil."); + } + + inline for (FLOATS) |T| { + num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin."); + num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos."); + num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan."); + + num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite."); + num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round."); + } } // Str Module @@ -107,7 +124,7 @@ comptime { } // Utils -const utils = @import("utils.zig"); + comptime { exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.decrefC, "decref"); diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index 0901722fd4..a3cfdcbb54 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -3,28 +3,67 @@ const always_inline = std.builtin.CallOptions.Modifier.always_inline; const math = std.math; const RocList = @import("list.zig").RocList; -pub fn atan(num: f64) callconv(.C) f64 { - return @call(.{ .modifier = always_inline }, math.atan, .{num}); +pub fn exportPow(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(base: T, exp: T) callconv(.C) T { + return std.math.pow(T, base, exp); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } -pub fn isFinite(num: f64) callconv(.C) bool { - return @call(.{ .modifier = always_inline }, math.isFinite, .{num}); +pub fn exportIsFinite(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T) callconv(.C) bool { + return std.math.isFinite(input); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } -pub fn powInt(base: i64, exp: i64) callconv(.C) i64 { - return @call(.{ .modifier = always_inline }, math.pow, .{ i64, base, exp }); +pub fn exportAsin(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T) callconv(.C) T { + return std.math.asin(input); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } -pub fn divCeil(numerator: i64, denominator: i64) callconv(.C) i64 { - return @call(.{ .modifier = always_inline }, math.divCeil, .{ i64, numerator, denominator }) catch unreachable; +pub fn exportAcos(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T) callconv(.C) T { + return std.math.acos(input); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } -pub fn acos(num: f64) callconv(.C) f64 { - return @call(.{ .modifier = always_inline }, math.acos, .{num}); +pub fn exportAtan(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T) callconv(.C) T { + return std.math.atan(input); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } -pub fn asin(num: f64) callconv(.C) f64 { - return @call(.{ .modifier = always_inline }, math.asin, .{num}); +pub fn exportRound(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T) callconv(.C) i64 { + return @floatToInt(i64, (@round(input))); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(a: T, b: T) callconv(.C) T { + return math.divCeil(T, a, b) catch unreachable; + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 { @@ -44,7 +83,3 @@ fn bytesToU32(arg: RocList, position: usize) u32 { const bytes = @ptrCast([*]const u8, arg.bytes); return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }); } - -pub fn round(num: f64) callconv(.C) i64 { - return @floatToInt(i32, (@round(num))); -} diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 976c4cb9a1..2623b2ad3b 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -1,17 +1,129 @@ +use std::ops::Index; + pub const OBJ_PATH: &str = env!( "BUILTINS_O", "Env var BUILTINS_O not found. Is there a problem with the build script?" ); -pub const NUM_ASIN: &str = "roc_builtins.num.asin"; -pub const NUM_ACOS: &str = "roc_builtins.num.acos"; -pub const NUM_ATAN: &str = "roc_builtins.num.atan"; -pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite"; -pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int"; -pub const NUM_DIV_CEIL: &str = "roc_builtins.num.div_ceil"; +#[derive(Debug, Default)] +pub struct IntrinsicName { + pub options: [&'static str; 14], +} + +impl IntrinsicName { + pub const fn default() -> Self { + Self { options: [""; 14] } + } +} + +#[repr(u8)] +pub enum DecWidth { + Dec, +} + +#[repr(u8)] +pub enum FloatWidth { + F32, + F64, + F128, +} + +#[repr(u8)] +pub enum IntWidth { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, +} + +impl Index for IntrinsicName { + type Output = str; + + fn index(&self, _: DecWidth) -> &Self::Output { + self.options[0] + } +} + +impl Index for IntrinsicName { + type Output = str; + + fn index(&self, index: FloatWidth) -> &Self::Output { + match index { + FloatWidth::F32 => self.options[1], + FloatWidth::F64 => self.options[2], + FloatWidth::F128 => self.options[3], + } + } +} + +impl Index for IntrinsicName { + type Output = str; + + fn index(&self, index: IntWidth) -> &Self::Output { + match index { + IntWidth::U8 => self.options[4], + IntWidth::U16 => self.options[5], + IntWidth::U32 => self.options[6], + IntWidth::U64 => self.options[7], + IntWidth::U128 => self.options[8], + IntWidth::I8 => self.options[9], + IntWidth::I16 => self.options[10], + IntWidth::I32 => self.options[11], + IntWidth::I64 => self.options[12], + IntWidth::I128 => self.options[13], + } + } +} + +#[macro_export] +macro_rules! float_intrinsic { + ($name:literal) => {{ + let mut output = IntrinsicName::default(); + + output.options[1] = concat!($name, ".f32"); + output.options[2] = concat!($name, ".f64"); + output.options[3] = concat!($name, ".f128"); + + output + }}; +} + +#[macro_export] +macro_rules! int_intrinsic { + ($name:literal) => {{ + let mut output = IntrinsicName::default(); + + output.options[4] = concat!($name, ".i8"); + output.options[5] = concat!($name, ".i16"); + output.options[6] = concat!($name, ".i32"); + output.options[7] = concat!($name, ".i64"); + output.options[8] = concat!($name, ".i128"); + output.options[9] = concat!($name, ".i8"); + output.options[10] = concat!($name, ".i16"); + output.options[11] = concat!($name, ".i32"); + output.options[12] = concat!($name, ".i64"); + output.options[13] = concat!($name, ".i128"); + + output + }}; +} + +pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin"); +pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos"); +pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan"); +pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite"); +pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int"); +pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil"); +pub const NUM_ROUND: IntrinsicName = float_intrinsic!("roc_builtins.num.round"); + pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; -pub const NUM_ROUND: &str = "roc_builtins.num.round"; pub const STR_INIT: &str = "roc_builtins.str.init"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 5ef03a3a42..bd13de5960 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -3,7 +3,7 @@ #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] use bumpalo::{collections::Vec, Bump}; -use roc_builtins::bitcode; +use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{ModuleName, TagName}; use roc_module::low_level::LowLevel; @@ -400,21 +400,21 @@ where } LowLevel::NumAcos => self.build_fn_call( sym, - bitcode::NUM_ACOS.to_string(), + bitcode::NUM_ACOS[FloatWidth::F64].to_string(), args, arg_layouts, ret_layout, ), LowLevel::NumAsin => self.build_fn_call( sym, - bitcode::NUM_ASIN.to_string(), + bitcode::NUM_ASIN[FloatWidth::F64].to_string(), args, arg_layouts, ret_layout, ), LowLevel::NumAtan => self.build_fn_call( sym, - bitcode::NUM_ATAN.to_string(), + bitcode::NUM_ATAN[FloatWidth::F64].to_string(), args, arg_layouts, ret_layout, @@ -437,7 +437,7 @@ where } LowLevel::NumPowInt => self.build_fn_call( sym, - bitcode::NUM_POW_INT.to_string(), + bitcode::NUM_POW_INT[IntWidth::I64].to_string(), args, arg_layouts, ret_layout, @@ -473,7 +473,7 @@ where } LowLevel::NumRound => self.build_fn_call( sym, - bitcode::NUM_ROUND.to_string(), + bitcode::NUM_ROUND[FloatWidth::F64].to_string(), args, arg_layouts, ret_layout, diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index c0245bdea0..fc94166f7e 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -48,7 +48,8 @@ use inkwell::{AddressSpace, IntPredicate}; use morphic_lib::{ CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions, UpdateMode, UpdateModeVar, }; -use roc_builtins::bitcode; +use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName}; +use roc_builtins::{float_intrinsic, int_intrinsic}; use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -430,6 +431,58 @@ pub fn module_from_builtins<'ctx>( module } +fn add_float_intrinsic<'ctx, F>( + ctx: &'ctx Context, + module: &Module<'ctx>, + name: &IntrinsicName, + construct_type: F, +) where + F: Fn(inkwell::types::FloatType<'ctx>) -> inkwell::types::FunctionType<'ctx>, +{ + macro_rules! check { + ($width:expr, $typ:expr) => { + let full_name = &name[$width]; + + if let Some(_) = module.get_function(full_name) { + // zig defined this function already + } else { + add_intrinsic(module, full_name, construct_type($typ)); + } + }; + } + + check!(FloatWidth::F32, ctx.f32_type()); + check!(FloatWidth::F64, ctx.f64_type()); + // check!(IntWidth::F128, ctx.i128_type()); +} + +fn add_int_intrinsic<'ctx, F>( + ctx: &'ctx Context, + module: &Module<'ctx>, + name: &IntrinsicName, + construct_type: F, +) where + F: Fn(inkwell::types::IntType<'ctx>) -> inkwell::types::FunctionType<'ctx>, +{ + macro_rules! check { + ($width:expr, $typ:expr) => { + let full_name = &name[$width]; + + if let Some(_) = module.get_function(full_name) { + // zig defined this function already + } else { + add_intrinsic(module, full_name, construct_type($typ)); + } + }; + } + + check!(IntWidth::I8, ctx.i8_type()); + check!(IntWidth::I16, ctx.i16_type()); + check!(IntWidth::I32, ctx.i32_type()); + check!(IntWidth::I64, ctx.i64_type()); + check!(IntWidth::I128, ctx.i128_type()); +} + fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { // List of all supported LLVM intrinsics: // @@ -438,7 +491,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { let i1_type = ctx.bool_type(); let i8_type = ctx.i8_type(); let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic); - let i16_type = ctx.i16_type(); let i32_type = ctx.i32_type(); let i64_type = ctx.i64_type(); let void_type = ctx.void_type(); @@ -475,114 +527,56 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { add_intrinsic(module, LLVM_STACK_SAVE, i8_ptr_type.fn_type(&[], false)); - add_intrinsic( - module, - LLVM_LOG_F64, - f64_type.fn_type(&[f64_type.into()], false), - ); - add_intrinsic( module, LLVM_LROUND_I64_F64, i64_type.fn_type(&[f64_type.into()], false), ); - add_intrinsic( - module, - LLVM_FABS_F64, - f64_type.fn_type(&[f64_type.into()], false), - ); + add_float_intrinsic(ctx, module, &LLVM_LOG, |t| t.fn_type(&[t.into()], false)); + add_float_intrinsic(ctx, module, &LLVM_POW, |t| { + t.fn_type(&[t.into(), t.into()], false) + }); + add_float_intrinsic(ctx, module, &LLVM_FABS, |t| t.fn_type(&[t.into()], false)); + add_float_intrinsic(ctx, module, &LLVM_SIN, |t| t.fn_type(&[t.into()], false)); + add_float_intrinsic(ctx, module, &LLVM_COS, |t| t.fn_type(&[t.into()], false)); + add_float_intrinsic(ctx, module, &LLVM_CEILING, |t| { + t.fn_type(&[t.into()], false) + }); + add_float_intrinsic(ctx, module, &LLVM_FLOOR, |t| t.fn_type(&[t.into()], false)); - add_intrinsic( - module, - LLVM_SIN_F64, - f64_type.fn_type(&[f64_type.into()], false), - ); - - add_intrinsic( - module, - LLVM_COS_F64, - f64_type.fn_type(&[f64_type.into()], false), - ); - - add_intrinsic( - module, - LLVM_POW_F64, - f64_type.fn_type(&[f64_type.into(), f64_type.into()], false), - ); - - add_intrinsic( - module, - LLVM_CEILING_F64, - f64_type.fn_type(&[f64_type.into()], false), - ); - - add_intrinsic( - module, - LLVM_FLOOR_F64, - f64_type.fn_type(&[f64_type.into()], false), - ); - - add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I8, { - let fields = [i8_type.into(), i1_type.into()]; + add_int_intrinsic(ctx, module, &LLVM_SADD_WITH_OVERFLOW, |t| { + let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) - .fn_type(&[i8_type.into(), i8_type.into()], false) + .fn_type(&[t.into(), t.into()], false) }); - add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I16, { - let fields = [i16_type.into(), i1_type.into()]; + add_int_intrinsic(ctx, module, &LLVM_SSUB_WITH_OVERFLOW, |t| { + let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) - .fn_type(&[i16_type.into(), i16_type.into()], false) + .fn_type(&[t.into(), t.into()], false) }); - add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I32, { - let fields = [i32_type.into(), i1_type.into()]; + add_int_intrinsic(ctx, module, &LLVM_SMUL_WITH_OVERFLOW, |t| { + let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) - .fn_type(&[i32_type.into(), i32_type.into()], false) - }); - - add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I64, { - let fields = [i64_type.into(), i1_type.into()]; - ctx.struct_type(&fields, false) - .fn_type(&[i64_type.into(), i64_type.into()], false) - }); - - add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I8, { - let fields = [i8_type.into(), i1_type.into()]; - ctx.struct_type(&fields, false) - .fn_type(&[i8_type.into(), i8_type.into()], false) - }); - - add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I16, { - let fields = [i16_type.into(), i1_type.into()]; - ctx.struct_type(&fields, false) - .fn_type(&[i16_type.into(), i16_type.into()], false) - }); - - add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I32, { - let fields = [i32_type.into(), i1_type.into()]; - ctx.struct_type(&fields, false) - .fn_type(&[i32_type.into(), i32_type.into()], false) - }); - - add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I64, { - let fields = [i64_type.into(), i1_type.into()]; - ctx.struct_type(&fields, false) - .fn_type(&[i64_type.into(), i64_type.into()], false) + .fn_type(&[t.into(), t.into()], false) }); } +const LLVM_POW: IntrinsicName = float_intrinsic!("llvm.pow"); +const LLVM_FABS: IntrinsicName = float_intrinsic!("llvm.fabs"); +static LLVM_SQRT: IntrinsicName = float_intrinsic!("llvm.sqrt"); +static LLVM_LOG: IntrinsicName = float_intrinsic!("llvm.log"); + +static LLVM_SIN: IntrinsicName = float_intrinsic!("llvm.sin"); +static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos"); +static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil"); +static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor"); + static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32"; -static LLVM_SQRT_F64: &str = "llvm.sqrt.f64"; -static LLVM_LOG_F64: &str = "llvm.log.f64"; static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; -static LLVM_FABS_F64: &str = "llvm.fabs.f64"; -static LLVM_SIN_F64: &str = "llvm.sin.f64"; -static LLVM_COS_F64: &str = "llvm.cos.f64"; -static LLVM_POW_F64: &str = "llvm.pow.f64"; -static LLVM_CEILING_F64: &str = "llvm.ceil.f64"; -static LLVM_FLOOR_F64: &str = "llvm.floor.f64"; // static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress"; static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0i8"; @@ -591,23 +585,13 @@ static LLVM_STACK_SAVE: &str = "llvm.stacksave"; static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp"; pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp"; -pub static LLVM_SADD_WITH_OVERFLOW_I8: &str = "llvm.sadd.with.overflow.i8"; -pub static LLVM_SADD_WITH_OVERFLOW_I16: &str = "llvm.sadd.with.overflow.i16"; -pub static LLVM_SADD_WITH_OVERFLOW_I32: &str = "llvm.sadd.with.overflow.i32"; -pub static LLVM_SADD_WITH_OVERFLOW_I64: &str = "llvm.sadd.with.overflow.i64"; -pub static LLVM_SADD_WITH_OVERFLOW_I128: &str = "llvm.sadd.with.overflow.i128"; - -pub static LLVM_SSUB_WITH_OVERFLOW_I8: &str = "llvm.ssub.with.overflow.i8"; -pub static LLVM_SSUB_WITH_OVERFLOW_I16: &str = "llvm.ssub.with.overflow.i16"; -pub static LLVM_SSUB_WITH_OVERFLOW_I32: &str = "llvm.ssub.with.overflow.i32"; -pub static LLVM_SSUB_WITH_OVERFLOW_I64: &str = "llvm.ssub.with.overflow.i64"; -pub static LLVM_SSUB_WITH_OVERFLOW_I128: &str = "llvm.ssub.with.overflow.i128"; - -pub static LLVM_SMUL_WITH_OVERFLOW_I64: &str = "llvm.smul.with.overflow.i64"; +const LLVM_SADD_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.sadd.with.overflow"); +const LLVM_SSUB_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.ssub.with.overflow"); +const LLVM_SMUL_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.smul.with.overflow"); fn add_intrinsic<'ctx>( module: &Module<'ctx>, - intrinsic_name: &'static str, + intrinsic_name: &str, fn_type: FunctionType<'ctx>, ) -> FunctionValue<'ctx> { add_func( @@ -5161,8 +5145,14 @@ fn run_low_level<'a, 'ctx, 'env>( Usize | Int128 | Int64 | Int32 | Int16 | Int8 => { build_int_unary_op(env, arg.into_int_value(), arg_builtin, op) } - Float128 | Float64 | Float32 => { - build_float_unary_op(env, arg.into_float_value(), op) + Float32 => { + build_float_unary_op(env, arg.into_float_value(), op, FloatWidth::F32) + } + Float64 => { + build_float_unary_op(env, arg.into_float_value(), op, FloatWidth::F64) + } + Float128 => { + build_float_unary_op(env, arg.into_float_value(), op, FloatWidth::F128) } _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout); @@ -5896,6 +5886,31 @@ fn throw_on_overflow<'a, 'ctx, 'env>( .unwrap() } +pub fn intwidth_from_builtin(builtin: Builtin<'_>, ptr_bytes: u32) -> IntWidth { + use IntWidth::*; + + match builtin { + Builtin::Int128 => I128, + Builtin::Int64 => I64, + Builtin::Int32 => I32, + Builtin::Int16 => I16, + Builtin::Int8 => I8, + Builtin::Usize => match ptr_bytes { + 4 => I32, + 8 => I64, + _ => unreachable!(), + }, + _ => unreachable!(), + } +} + +fn intwidth_from_layout(layout: Layout<'_>, ptr_bytes: u32) -> IntWidth { + match layout { + Layout::Builtin(builtin) => intwidth_from_builtin(builtin, ptr_bytes), + _ => unreachable!(), + } +} + fn build_int_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -5910,62 +5925,54 @@ fn build_int_binop<'a, 'ctx, 'env>( let bd = env.builder; + let int_width = intwidth_from_layout(*lhs_layout, env.ptr_bytes); + match op { NumAdd => { - let intrinsic = match lhs_layout { - Layout::Builtin(Builtin::Int8) => LLVM_SADD_WITH_OVERFLOW_I8, - Layout::Builtin(Builtin::Int16) => LLVM_SADD_WITH_OVERFLOW_I16, - Layout::Builtin(Builtin::Int32) => LLVM_SADD_WITH_OVERFLOW_I32, - Layout::Builtin(Builtin::Int64) => LLVM_SADD_WITH_OVERFLOW_I64, - Layout::Builtin(Builtin::Int128) => LLVM_SADD_WITH_OVERFLOW_I128, - Layout::Builtin(Builtin::Usize) => match env.ptr_bytes { - 4 => LLVM_SADD_WITH_OVERFLOW_I32, - 8 => LLVM_SADD_WITH_OVERFLOW_I64, - other => panic!("invalid ptr_bytes {}", other), - }, - _ => unreachable!(), - }; - let result = env - .call_intrinsic(intrinsic, &[lhs.into(), rhs.into()]) + .call_intrinsic( + &LLVM_SADD_WITH_OVERFLOW[int_width], + &[lhs.into(), rhs.into()], + ) .into_struct_value(); throw_on_overflow(env, parent, result, "integer addition overflowed!") } NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), - NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), + NumAddChecked => env.call_intrinsic( + &LLVM_SADD_WITH_OVERFLOW[int_width], + &[lhs.into(), rhs.into()], + ), NumSub => { - let intrinsic = match lhs_layout { - Layout::Builtin(Builtin::Int8) => LLVM_SSUB_WITH_OVERFLOW_I8, - Layout::Builtin(Builtin::Int16) => LLVM_SSUB_WITH_OVERFLOW_I16, - Layout::Builtin(Builtin::Int32) => LLVM_SSUB_WITH_OVERFLOW_I32, - Layout::Builtin(Builtin::Int64) => LLVM_SSUB_WITH_OVERFLOW_I64, - Layout::Builtin(Builtin::Int128) => LLVM_SSUB_WITH_OVERFLOW_I128, - Layout::Builtin(Builtin::Usize) => match env.ptr_bytes { - 4 => LLVM_SSUB_WITH_OVERFLOW_I32, - 8 => LLVM_SSUB_WITH_OVERFLOW_I64, - other => panic!("invalid ptr_bytes {}", other), - }, - _ => unreachable!("invalid layout {:?}", lhs_layout), - }; - let result = env - .call_intrinsic(intrinsic, &[lhs.into(), rhs.into()]) + .call_intrinsic( + &LLVM_SSUB_WITH_OVERFLOW[int_width], + &[lhs.into(), rhs.into()], + ) .into_struct_value(); throw_on_overflow(env, parent, result, "integer subtraction overflowed!") } NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(), - NumSubChecked => env.call_intrinsic(LLVM_SSUB_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), + NumSubChecked => env.call_intrinsic( + &LLVM_SSUB_WITH_OVERFLOW[int_width], + &[lhs.into(), rhs.into()], + ), NumMul => { let result = env - .call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]) + .call_intrinsic( + &LLVM_SMUL_WITH_OVERFLOW[int_width], + &[lhs.into(), rhs.into()], + ) .into_struct_value(); throw_on_overflow(env, parent, result, "integer multiplication overflowed!") } NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(), - NumMulChecked => env.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), + NumMulChecked => env.call_intrinsic( + &LLVM_SMUL_WITH_OVERFLOW[int_width], + &[lhs.into(), rhs.into()], + ), NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(), NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(), @@ -6039,11 +6046,17 @@ fn build_int_binop<'a, 'ctx, 'env>( phi.as_basic_value() } } + NumPowInt => call_bitcode_fn( + env, + &[lhs.into(), rhs.into()], + &bitcode::NUM_POW_INT[int_width], + ), NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), - NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_POW_INT), - NumDivCeilUnchecked => { - call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_DIV_CEIL) - } + NumDivCeilUnchecked => call_bitcode_fn( + env, + &[lhs.into(), rhs.into()], + &bitcode::NUM_DIV_CEIL[int_width], + ), NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(), NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(), @@ -6085,6 +6098,17 @@ pub fn build_num_binop<'a, 'ctx, 'env>( { use roc_mono::layout::Builtin::*; + let float_binop = |float_width| { + build_float_binop( + env, + parent, + lhs_arg.into_float_value(), + rhs_arg.into_float_value(), + float_width, + op, + ) + }; + match lhs_builtin { Usize | Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop( env, @@ -6095,15 +6119,11 @@ pub fn build_num_binop<'a, 'ctx, 'env>( rhs_layout, op, ), - Float128 | Float64 | Float32 => build_float_binop( - env, - parent, - lhs_arg.into_float_value(), - lhs_layout, - rhs_arg.into_float_value(), - rhs_layout, - op, - ), + + Float32 => float_binop(FloatWidth::F32), + Float64 => float_binop(FloatWidth::F64), + Float128 => float_binop(FloatWidth::F128), + Decimal => { build_dec_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op) } @@ -6122,9 +6142,8 @@ fn build_float_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, lhs: FloatValue<'ctx>, - _lhs_layout: &Layout<'a>, rhs: FloatValue<'ctx>, - _rhs_layout: &Layout<'a>, + float_width: FloatWidth, op: LowLevel, ) -> BasicValueEnum<'ctx> { use inkwell::FloatPredicate::*; @@ -6140,7 +6159,8 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_add(lhs, rhs, "add_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) + .into_int_value(); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); @@ -6161,7 +6181,8 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_add(lhs, rhs, "add_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) + .into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( @@ -6189,7 +6210,8 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_sub(lhs, rhs, "sub_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) + .into_int_value(); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); @@ -6210,7 +6232,8 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_sub(lhs, rhs, "sub_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) + .into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( @@ -6238,7 +6261,8 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_mul(lhs, rhs, "mul_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) + .into_int_value(); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); @@ -6259,7 +6283,8 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_mul(lhs, rhs, "mul_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) + .into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( @@ -6286,7 +6311,7 @@ fn build_float_binop<'a, 'ctx, 'env>( NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(), NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(), NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(), - NumPow => env.call_intrinsic(LLVM_POW_F64, &[lhs.into(), rhs.into()]), + NumPow => env.call_intrinsic(&LLVM_POW[float_width], &[lhs.into(), rhs.into()]), _ => { unreachable!("Unrecognized int binary operation: {:?}", op); } @@ -6528,6 +6553,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, arg: FloatValue<'ctx>, op: LowLevel, + float_width: FloatWidth, ) -> BasicValueEnum<'ctx> { use roc_module::low_level::LowLevel::*; @@ -6536,34 +6562,72 @@ fn build_float_unary_op<'a, 'ctx, 'env>( // TODO: Handle different sized floats match op { NumNeg => bd.build_float_neg(arg, "negate_float").into(), - NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]), - NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]), - NumLogUnchecked => env.call_intrinsic(LLVM_LOG_F64, &[arg.into()]), - NumRound => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ROUND), - NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]), - NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]), + NumAbs => env.call_intrinsic(&LLVM_FABS[float_width], &[arg.into()]), + NumSqrtUnchecked => env.call_intrinsic(&LLVM_SQRT[float_width], &[arg.into()]), + NumLogUnchecked => env.call_intrinsic(&LLVM_LOG[float_width], &[arg.into()]), NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */ NumCeiling => env.builder.build_cast( InstructionOpcode::FPToSI, - env.call_intrinsic(LLVM_CEILING_F64, &[arg.into()]), + env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]), env.context.i64_type(), "num_ceiling", ), NumFloor => env.builder.build_cast( InstructionOpcode::FPToSI, - env.call_intrinsic(LLVM_FLOOR_F64, &[arg.into()]), + env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]), env.context.i64_type(), "num_floor", ), - NumIsFinite => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_IS_FINITE), - NumAtan => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ATAN), - NumAcos => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ACOS), - NumAsin => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ASIN), + NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]), + NumRound => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND[float_width]), + + // trigonometry + NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]), + NumCos => env.call_intrinsic(&LLVM_COS[float_width], &[arg.into()]), + + NumAtan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ATAN[float_width]), + NumAcos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ACOS[float_width]), + NumAsin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ASIN[float_width]), + _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } } } + +pub fn call_bitcode_int_fn<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + fn_name: &str, + args: &[BasicValueEnum<'ctx>], + int_width: IntWidth, +) -> BasicValueEnum<'ctx> { + match int_width { + IntWidth::U8 => call_bitcode_fn(env, args, &format!("{}_u8", fn_name)), + IntWidth::U16 => call_bitcode_fn(env, args, &format!("{}_u16", fn_name)), + IntWidth::U32 => call_bitcode_fn(env, args, &format!("{}_u32", fn_name)), + IntWidth::U64 => call_bitcode_fn(env, args, &format!("{}_u64", fn_name)), + IntWidth::U128 => call_bitcode_fn(env, args, &format!("{}_u128", fn_name)), + IntWidth::I8 => call_bitcode_fn(env, args, &format!("{}_i8", fn_name)), + IntWidth::I16 => call_bitcode_fn(env, args, &format!("{}_i16", fn_name)), + IntWidth::I32 => call_bitcode_fn(env, args, &format!("{}_i32", fn_name)), + IntWidth::I64 => call_bitcode_fn(env, args, &format!("{}_i64", fn_name)), + IntWidth::I128 => call_bitcode_fn(env, args, &format!("{}_i128", fn_name)), + } +} + +pub fn call_bitcode_float_fn<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + fn_name: &str, + args: &[BasicValueEnum<'ctx>], + float_width: FloatWidth, +) -> BasicValueEnum<'ctx> { + match float_width { + FloatWidth::F32 => call_bitcode_fn(env, args, &format!("{}_f32", fn_name)), + FloatWidth::F64 => call_bitcode_fn(env, args, &format!("{}_f64", fn_name)), + FloatWidth::F128 => todo!("suport 128-bit floats"), + } +} + fn define_global_str_literal_ptr<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, message: &str, diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index ddc6cd645b..3d80912689 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -502,38 +502,6 @@ pub fn list_walk_generic<'a, 'ctx, 'env>( env.builder.build_load(result_ptr, "load_result") } -#[allow(dead_code)] -#[repr(u8)] -enum IntWidth { - U8, - U16, - U32, - U64, - U128, - I8, - I16, - I32, - I64, - I128, - Usize, -} - -impl From> for IntWidth { - fn from(builtin: Builtin) -> Self { - use IntWidth::*; - - match builtin { - Builtin::Int128 => I128, - Builtin::Int64 => I64, - Builtin::Int32 => I32, - Builtin::Int16 => I16, - Builtin::Int8 => I8, - Builtin::Usize => Usize, - _ => unreachable!(), - } - } -} - /// List.range : Int a, Int a -> List (Int a) pub fn list_range<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -552,7 +520,10 @@ pub fn list_range<'a, 'ctx, 'env>( let int_width = env .context .i8_type() - .const_int(IntWidth::from(builtin) as u64, false) + .const_int( + crate::llvm::build::intwidth_from_builtin(builtin, env.ptr_bytes) as u64, + false, + ) .into(); call_bitcode_fn(