From 630897b7793ef4f6d3be3607790209b862ea3179 Mon Sep 17 00:00:00 2001 From: David Mak Date: Mon, 9 Oct 2023 16:02:15 +0800 Subject: [PATCH 01/10] standalone: Do not output sign if float is NaN Matches behavior in Python. --- nac3standalone/demo/demo.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nac3standalone/demo/demo.c b/nac3standalone/demo/demo.c index 8f79e75..52af7b6 100644 --- a/nac3standalone/demo/demo.c +++ b/nac3standalone/demo/demo.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -33,7 +34,11 @@ void output_uint64(uint64_t x) { } void output_float64(double x) { - printf("%f\n", x); + if (isnan(x)) { + puts("nan"); + } else { + printf("%f\n", x); + } } void output_asciiart(int32_t x) { -- 2.44.1 From 95810d422962d906262199c3a0ae53825b32d748 Mon Sep 17 00:00:00 2001 From: David Mak Date: Fri, 6 Oct 2023 17:34:14 +0800 Subject: [PATCH 02/10] core: Remove {ceil64,floor64,round,round64} These are not present in NumPy or Artiq. --- nac3core/src/toplevel/builtins.rs | 164 ------------------------------ 1 file changed, 164 deletions(-) diff --git a/nac3core/src/toplevel/builtins.rs b/nac3core/src/toplevel/builtins.rs index 1168a3c..cdb8f31 100644 --- a/nac3core/src/toplevel/builtins.rs +++ b/nac3core/src/toplevel/builtins.rs @@ -553,86 +553,6 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { )))), loc: None, })), - Arc::new(RwLock::new(TopLevelDef::Function { - name: "round".into(), - simple_name: "round".into(), - signature: primitives.1.add_ty(TypeEnum::TFunc(FunSignature { - args: vec![FuncArg { name: "n".into(), ty: float, default_value: None }], - ret: int32, - vars: Default::default(), - })), - var_id: Default::default(), - instance_to_symbol: Default::default(), - instance_to_stmt: Default::default(), - resolver: None, - codegen_callback: Some(Arc::new(GenCall::new(Box::new( - |ctx, _, _, args, generator| { - let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, ctx.primitives.float)?; - let round_intrinsic = - ctx.module.get_function("llvm.round.f64").unwrap_or_else(|| { - let float = ctx.ctx.f64_type(); - let fn_type = float.fn_type(&[float.into()], false); - ctx.module.add_function("llvm.round.f64", fn_type, None) - }); - let val = ctx - .builder - .build_call(round_intrinsic, &[arg.into()], "round") - .try_as_basic_value() - .left() - .unwrap(); - Ok(Some( - ctx.builder - .build_float_to_signed_int( - val.into_float_value(), - ctx.ctx.i32_type(), - "fptosi", - ) - .into(), - )) - }, - )))), - loc: None, - })), - Arc::new(RwLock::new(TopLevelDef::Function { - name: "round64".into(), - simple_name: "round64".into(), - signature: primitives.1.add_ty(TypeEnum::TFunc(FunSignature { - args: vec![FuncArg { name: "n".into(), ty: float, default_value: None }], - ret: int64, - vars: Default::default(), - })), - var_id: Default::default(), - instance_to_symbol: Default::default(), - instance_to_stmt: Default::default(), - resolver: None, - codegen_callback: Some(Arc::new(GenCall::new(Box::new( - |ctx, _, _, args, generator| { - let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, ctx.primitives.float)?; - let round_intrinsic = - ctx.module.get_function("llvm.round.f64").unwrap_or_else(|| { - let float = ctx.ctx.f64_type(); - let fn_type = float.fn_type(&[float.into()], false); - ctx.module.add_function("llvm.round.f64", fn_type, None) - }); - let val = ctx - .builder - .build_call(round_intrinsic, &[arg.into()], "round") - .try_as_basic_value() - .left() - .unwrap(); - Ok(Some( - ctx.builder - .build_float_to_signed_int( - val.into_float_value(), - ctx.ctx.i64_type(), - "fptosi", - ) - .into(), - )) - }, - )))), - loc: None, - })), Arc::new(RwLock::new(TopLevelDef::Function { name: "range".into(), simple_name: "range".into(), @@ -855,46 +775,6 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { )))), loc: None, })), - Arc::new(RwLock::new(TopLevelDef::Function { - name: "floor64".into(), - simple_name: "floor64".into(), - signature: primitives.1.add_ty(TypeEnum::TFunc(FunSignature { - args: vec![FuncArg { name: "n".into(), ty: float, default_value: None }], - ret: int64, - vars: Default::default(), - })), - var_id: Default::default(), - instance_to_symbol: Default::default(), - instance_to_stmt: Default::default(), - resolver: None, - codegen_callback: Some(Arc::new(GenCall::new(Box::new( - |ctx, _, _, args, generator| { - let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, ctx.primitives.float)?; - let floor_intrinsic = - ctx.module.get_function("llvm.floor.f64").unwrap_or_else(|| { - let float = ctx.ctx.f64_type(); - let fn_type = float.fn_type(&[float.into()], false); - ctx.module.add_function("llvm.floor.f64", fn_type, None) - }); - let val = ctx - .builder - .build_call(floor_intrinsic, &[arg.into()], "floor") - .try_as_basic_value() - .left() - .unwrap(); - Ok(Some( - ctx.builder - .build_float_to_signed_int( - val.into_float_value(), - ctx.ctx.i64_type(), - "fptosi", - ) - .into(), - )) - }, - )))), - loc: None, - })), Arc::new(RwLock::new(TopLevelDef::Function { name: "ceil".into(), simple_name: "ceil".into(), @@ -935,46 +815,6 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { )))), loc: None, })), - Arc::new(RwLock::new(TopLevelDef::Function { - name: "ceil64".into(), - simple_name: "ceil64".into(), - signature: primitives.1.add_ty(TypeEnum::TFunc(FunSignature { - args: vec![FuncArg { name: "n".into(), ty: float, default_value: None }], - ret: int64, - vars: Default::default(), - })), - var_id: Default::default(), - instance_to_symbol: Default::default(), - instance_to_stmt: Default::default(), - resolver: None, - codegen_callback: Some(Arc::new(GenCall::new(Box::new( - |ctx, _, _, args, generator| { - let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, ctx.primitives.float)?; - let ceil_intrinsic = - ctx.module.get_function("llvm.ceil.f64").unwrap_or_else(|| { - let float = ctx.ctx.f64_type(); - let fn_type = float.fn_type(&[float.into()], false); - ctx.module.add_function("llvm.ceil.f64", fn_type, None) - }); - let val = ctx - .builder - .build_call(ceil_intrinsic, &[arg.into()], "ceil") - .try_as_basic_value() - .left() - .unwrap(); - Ok(Some( - ctx.builder - .build_float_to_signed_int( - val.into_float_value(), - ctx.ctx.i64_type(), - "fptosi", - ) - .into(), - )) - }, - )))), - loc: None, - })), Arc::new(RwLock::new({ let list_var = primitives.1.get_fresh_var(Some("L".into()), None); let list = primitives.1.add_ty(TypeEnum::TList { ty: list_var.0 }); @@ -1255,15 +1095,11 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { "uint32", "uint64", "float", - "round", - "round64", "range", "str", "bool", "floor", - "floor64", "ceil", - "ceil64", "len", "min", "max", -- 2.44.1 From 068f0d9faf809fcd35c02f78476597f6193e7c6d Mon Sep 17 00:00:00 2001 From: David Mak Date: Fri, 6 Oct 2023 17:35:18 +0800 Subject: [PATCH 03/10] core: Do not cast floor/ceil result to int NumPy explicitly states that the return type of the floor/ceil is float. --- nac3core/src/toplevel/builtins.rs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/nac3core/src/toplevel/builtins.rs b/nac3core/src/toplevel/builtins.rs index cdb8f31..1b9ef28 100644 --- a/nac3core/src/toplevel/builtins.rs +++ b/nac3core/src/toplevel/builtins.rs @@ -762,15 +762,7 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { .try_as_basic_value() .left() .unwrap(); - Ok(Some( - ctx.builder - .build_float_to_signed_int( - val.into_float_value(), - ctx.ctx.i32_type(), - "fptosi", - ) - .into(), - )) + Ok(val.into()) }, )))), loc: None, @@ -802,15 +794,7 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { .try_as_basic_value() .left() .unwrap(); - Ok(Some( - ctx.builder - .build_float_to_signed_int( - val.into_float_value(), - ctx.ctx.i32_type(), - "fptosi", - ) - .into(), - )) + Ok(val.into()) }, )))), loc: None, -- 2.44.1 From 7cf763498562e537e1c2f840c4ac75daeb1c86c1 Mon Sep 17 00:00:00 2001 From: David Mak Date: Tue, 10 Oct 2023 14:56:16 +0800 Subject: [PATCH 04/10] core: Add create_fn_by_* functions Used for abstracting the creation of function from different sources. --- nac3core/src/toplevel/builtins.rs | 186 +++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 2 deletions(-) diff --git a/nac3core/src/toplevel/builtins.rs b/nac3core/src/toplevel/builtins.rs index 1b9ef28..5e3f112 100644 --- a/nac3core/src/toplevel/builtins.rs +++ b/nac3core/src/toplevel/builtins.rs @@ -5,7 +5,13 @@ use crate::{ }, symbol_resolver::SymbolValue, }; -use inkwell::{types::BasicType, FloatPredicate, IntPredicate}; +use inkwell::{ + attributes::{Attribute, AttributeLoc}, + types::{BasicType, BasicMetadataTypeEnum}, + values::BasicMetadataValueEnum, + FloatPredicate, + IntPredicate +}; type BuiltinInfo = (Vec<(Arc>, Option)>, &'static [&'static str]); @@ -78,6 +84,182 @@ pub fn get_exn_constructor( (fun_def, class_def, signature, exn_type) } +/// Creates a NumPy [TopLevelDef] function by code generation. +/// +/// * `name`: The name of the implemented NumPy function. +/// * `ret_ty`: The return type of this function. +/// * `param_ty`: The parameters accepted by this function, represented by a tuple of the +/// [parameter type][Type] and the parameter symbol name. +/// * `codegen_callback`: A lambda generating LLVM IR for the implementation of this function. +fn create_fn_by_codegen( + primitives: &mut (PrimitiveStore, Unifier), + var_map: &HashMap, + name: &'static str, + ret_ty: Type, + param_ty: &[(Type, &'static str)], + codegen_callback: GenCallCallback, +) -> Arc> { + Arc::new(RwLock::new(TopLevelDef::Function { + name: name.into(), + simple_name: name.into(), + signature: primitives.1.add_ty(TypeEnum::TFunc(FunSignature { + args: param_ty.iter().map(|p| FuncArg { + name: p.1.into(), + ty: p.0, + default_value: None, + }).collect(), + ret: ret_ty.clone(), + vars: var_map.clone(), + })), + var_id: Default::default(), + instance_to_symbol: Default::default(), + instance_to_stmt: Default::default(), + resolver: None, + codegen_callback: Some(Arc::new(GenCall::new(codegen_callback))), + loc: None, + })) +} + +/// Creates a NumPy [TopLevelDef] function using an LLVM intrinsic. +/// +/// * `name`: The name of the implemented NumPy function. +/// * `ret_ty`: The return type of this function. +/// * `param_ty`: The parameters accepted by this function, represented by a tuple of the +/// [parameter type][Type] and the parameter symbol name. +/// * `intrinsic_fn`: The fully-qualified name of the LLVM intrinsic function. +fn create_fn_by_intrinsic( + primitives: &mut (PrimitiveStore, Unifier), + var_map: &HashMap, + name: &'static str, + ret_ty: Type, + params: &[(Type, &'static str)], + intrinsic_fn: &'static str, +) -> Arc> { + let param_tys = params.iter() + .map(|p| p.0) + .collect_vec(); + + create_fn_by_codegen( + primitives, + var_map, + name, + ret_ty, + params, + Box::new(move |ctx, _, fun, args, generator| { + let args_ty = fun.0.args.iter().map(|a| a.ty).collect_vec(); + + assert!(param_tys.iter().zip(&args_ty) + .all(|(expected, actual)| ctx.unifier.unioned(*expected, *actual))); + + let args_val = args_ty.iter().zip_eq(args.iter()) + .map(|(ty, arg)| { + arg.1.clone() + .to_basic_value_enum(ctx, generator, ty.clone()) + .unwrap() + }) + .map_into::() + .collect_vec(); + + let intrinsic_fn = ctx.module.get_function(intrinsic_fn).unwrap_or_else(|| { + let ret_llvm_ty = ctx.get_llvm_abi_type(generator, ret_ty.clone()); + let param_llvm_ty = param_tys.iter() + .map(|p| ctx.get_llvm_abi_type(generator, *p)) + .map_into::() + .collect_vec(); + let fn_type = ret_llvm_ty.fn_type(param_llvm_ty.as_slice(), false); + + ctx.module.add_function(intrinsic_fn, fn_type, None) + }); + + let call = ctx.builder + .build_call(intrinsic_fn, args_val.as_slice(), name); + + let val = call.try_as_basic_value() + .left() + .unwrap(); + Ok(val.into()) + }), + ) +} + +/// Creates a unary NumPy [TopLevelDef] function using an extern function (e.g. from `libc` or +/// `libm`). +/// +/// * `name`: The name of the implemented NumPy function. +/// * `ret_ty`: The return type of this function. +/// * `param_ty`: The parameters accepted by this function, represented by a tuple of the +/// [parameter type][Type] and the parameter symbol name. +/// * `extern_fn`: The fully-qualified name of the extern function used as the implementation. +/// * `attrs`: The list of attributes to apply to this function declaration. Note that `nounwind` is +/// already implied by the C ABI. +fn create_fn_by_extern( + primitives: &mut (PrimitiveStore, Unifier), + var_map: &HashMap, + name: &'static str, + ret_ty: Type, + params: &[(Type, &'static str)], + extern_fn: &'static str, + attrs: &'static [&str], +) -> Arc> { + let param_tys = params.iter() + .map(|p| p.0) + .collect_vec(); + + create_fn_by_codegen( + primitives, + var_map, + name, + ret_ty, + params, + Box::new(move |ctx, _, fun, args, generator| { + let args_ty = fun.0.args.iter().map(|a| a.ty).collect_vec(); + + assert!(param_tys.iter().zip(&args_ty) + .all(|(expected, actual)| ctx.unifier.unioned(*expected, *actual))); + + let args_val = args_ty.iter().zip_eq(args.iter()) + .map(|(ty, arg)| { + arg.1.clone() + .to_basic_value_enum(ctx, generator, ty.clone()) + .unwrap() + }) + .map_into::() + .collect_vec(); + + let intrinsic_fn = ctx.module.get_function(extern_fn).unwrap_or_else(|| { + let ret_llvm_ty = ctx.get_llvm_abi_type(generator, ret_ty.clone()); + let param_llvm_ty = param_tys.iter() + .map(|p| ctx.get_llvm_abi_type(generator, *p)) + .map_into::() + .collect_vec(); + let fn_type = ret_llvm_ty.fn_type(param_llvm_ty.as_slice(), false); + let func = ctx.module.add_function(extern_fn, fn_type, None); + func.add_attribute( + AttributeLoc::Function, + ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id("nounwind"), 0) + ); + + for attr in attrs { + func.add_attribute( + AttributeLoc::Function, + ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0) + ); + } + + func + }); + + let call = ctx.builder + .build_call(intrinsic_fn, &args_val, name); + + let val = call.try_as_basic_value() + .left() + .unwrap(); + Ok(val.into()) + }), + ) +} + pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { let int32 = primitives.0.int32; let int64 = primitives.0.int64; @@ -981,7 +1163,7 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { signature: primitives.1.add_ty(TypeEnum::TFunc(FunSignature { args: vec![FuncArg { name: "n".into(), ty: num_ty.0, default_value: None }], ret: num_ty.0, - vars: var_map, + vars: var_map.clone(), })), var_id: Default::default(), instance_to_symbol: Default::default(), -- 2.44.1 From 316f0824d83e05f1cef7b75a9821d944e6a3832d Mon Sep 17 00:00:00 2001 From: David Mak Date: Mon, 9 Oct 2023 16:56:33 +0800 Subject: [PATCH 05/10] flake: Add scipy --- flake.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index cdfbddf..10f525d 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ passthru.cargoLock = cargoLock; nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_14.clang packages.x86_64-linux.clang-unwrapped pkgs.llvmPackages_14.llvm.out llvm-nac3 ]; buildInputs = [ pkgs.python3 llvm-nac3 ]; - checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ])) ]; + checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ps.scipy ])) ]; checkPhase = '' echo "Checking nac3standalone demos..." @@ -94,7 +94,7 @@ }) ]; buildInputs = [ - (python3-mimalloc.withPackages(ps: [ ps.numpy ps.jsonschema nac3artiq-instrumented ])) + (python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ps.jsonschema nac3artiq-instrumented ])) pkgs.llvmPackages_14.llvm.out ]; phases = [ "buildPhase" "installPhase" ]; @@ -151,7 +151,7 @@ rustc # runtime dependencies lld_14 # for running kernels on the host - (packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ ps.numpy ])) + (packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ])) # development tools cargo-insta clippy -- 2.44.1 From 60ad100fbb294b449fbf72394249610c2c6666c5 Mon Sep 17 00:00:00 2001 From: David Mak Date: Tue, 10 Oct 2023 16:56:38 +0800 Subject: [PATCH 06/10] core: Implement and expose {isinf,isnan} --- nac3core/src/codegen/irrt/irrt.c | 8 +++++ nac3core/src/codegen/irrt/mod.rs | 42 +++++++++++++++++++++++- nac3core/src/toplevel/builtins.rs | 46 ++++++++++++++++++++++++++- nac3standalone/demo/demo.c | 8 +++++ nac3standalone/demo/interpret_demo.py | 16 +++++++++- nac3standalone/demo/src/math.py | 25 +++++++++++++++ 6 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 nac3standalone/demo/src/math.py diff --git a/nac3core/src/codegen/irrt/irrt.c b/nac3core/src/codegen/irrt/irrt.c index a3e282e..410813d 100644 --- a/nac3core/src/codegen/irrt/irrt.c +++ b/nac3core/src/codegen/irrt/irrt.c @@ -137,4 +137,12 @@ int32_t __nac3_list_slice_assign_var_size( return dest_arr_len - (dest_end - dest_ind) - 1; } return dest_arr_len; +} + +int32_t __nac3_isinf(double x) { + return __builtin_isinf(x); +} + +int32_t __nac3_isnan(double x) { + return __builtin_isnan(x); } \ No newline at end of file diff --git a/nac3core/src/codegen/irrt/mod.rs b/nac3core/src/codegen/irrt/mod.rs index a566e6b..527700f 100644 --- a/nac3core/src/codegen/irrt/mod.rs +++ b/nac3core/src/codegen/irrt/mod.rs @@ -7,7 +7,7 @@ use inkwell::{ memory_buffer::MemoryBuffer, module::Module, types::BasicTypeEnum, - values::{IntValue, PointerValue}, + values::{FloatValue, IntValue, PointerValue}, AddressSpace, IntPredicate, }; use nac3parser::ast::Expr; @@ -432,3 +432,43 @@ pub fn list_slice_assignment<'ctx, 'a>( ctx.builder.build_unconditional_branch(cont_bb); ctx.builder.position_at_end(cont_bb); } + +/// Generates a call to `isinf` in IR. Returns an `i1` representing the result. +pub fn call_isinf<'ctx, 'a>( + generator: &mut dyn CodeGenerator, + ctx: &CodeGenContext<'ctx, 'a>, + v: FloatValue<'ctx>, +) -> IntValue<'ctx> { + let intrinsic_fn = ctx.module.get_function("__nac3_isinf").unwrap_or_else(|| { + let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false); + ctx.module.add_function("__nac3_isinf", fn_type, None) + }); + + let ret = ctx.builder + .build_call(intrinsic_fn, &[v.into()], "isinf") + .try_as_basic_value() + .unwrap_left() + .into_int_value(); + + generator.bool_to_i1(ctx, ret) +} + +/// Generates a call to `isnan` in IR. Returns an `i1` representing the result. +pub fn call_isnan<'ctx, 'a>( + generator: &mut dyn CodeGenerator, + ctx: &CodeGenContext<'ctx, 'a>, + v: FloatValue<'ctx>, +) -> IntValue<'ctx> { + let intrinsic_fn = ctx.module.get_function("__nac3_isnan").unwrap_or_else(|| { + let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false); + ctx.module.add_function("__nac3_isnan", fn_type, None) + }); + + let ret = ctx.builder + .build_call(intrinsic_fn, &[v.into()], "isnan") + .try_as_basic_value() + .unwrap_left() + .into_int_value(); + + generator.bool_to_i1(ctx, ret) +} diff --git a/nac3core/src/toplevel/builtins.rs b/nac3core/src/toplevel/builtins.rs index 5e3f112..6f3488d 100644 --- a/nac3core/src/toplevel/builtins.rs +++ b/nac3core/src/toplevel/builtins.rs @@ -1,7 +1,9 @@ use super::*; use crate::{ codegen::{ - expr::destructure_range, irrt::calculate_len_for_slice_range, stmt::exn_constructor, + expr::destructure_range, + irrt::{calculate_len_for_slice_range, call_isinf, call_isnan}, + stmt::exn_constructor, }, symbol_resolver::SymbolValue, }; @@ -1226,6 +1228,46 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { )))), loc: None, })), + create_fn_by_codegen( + primitives, + &var_map, + "isnan", + boolean, + &[(float, "x")], + Box::new(|ctx, _, fun, args, generator| { + let float = ctx.primitives.float; + + let x_ty = fun.0.args[0].ty; + let x_val = args[0].1.clone() + .to_basic_value_enum(ctx, generator, x_ty)?; + + assert!(ctx.unifier.unioned(x_ty, float)); + + let val = call_isnan(generator, ctx, x_val.into_float_value()); + + Ok(Some(val.into())) + }), + ), + create_fn_by_codegen( + primitives, + &var_map, + "isinf", + boolean, + &[(float, "x")], + Box::new(|ctx, _, fun, args, generator| { + let float = ctx.primitives.float; + + let x_ty = fun.0.args[0].ty; + let x_val = args[0].1.clone() + .to_basic_value_enum(ctx, generator, x_ty)?; + + assert!(ctx.unifier.unioned(x_ty, float)); + + let val = call_isinf(generator, ctx, x_val.into_float_value()); + + Ok(Some(val.into())) + }), + ), Arc::new(RwLock::new(TopLevelDef::Function { name: "Some".into(), simple_name: "Some".into(), @@ -1270,6 +1312,8 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { "min", "max", "abs", + "isnan", + "isinf", "Some", ], ) diff --git a/nac3standalone/demo/demo.c b/nac3standalone/demo/demo.c index 52af7b6..c02046b 100644 --- a/nac3standalone/demo/demo.c +++ b/nac3standalone/demo/demo.c @@ -13,6 +13,14 @@ #error "Unsupported platform - Platform is not 32-bit or 64-bit" #endif +double dbl_nan(void) { + return NAN; +} + +double dbl_inf(void) { + return INFINITY; +} + void output_bool(bool x) { puts(x ? "True" : "False"); } diff --git a/nac3standalone/demo/interpret_demo.py b/nac3standalone/demo/interpret_demo.py index 6eac7bb..9c62e58 100755 --- a/nac3standalone/demo/interpret_demo.py +++ b/nac3standalone/demo/interpret_demo.py @@ -3,6 +3,7 @@ import sys import importlib.util import importlib.machinery +import numpy as np import pathlib from numpy import int32, int64, uint32, uint64 @@ -42,6 +43,12 @@ def Some(v: T) -> Option[T]: none = Option(None) def patch(module): + def dbl_nan(): + return np.nan + + def dbl_inf(): + return np.inf + def output_asciiart(x): if x < 0: sys.stdout.write("\n") @@ -56,7 +63,11 @@ def patch(module): def extern(fun): name = fun.__name__ - if name == "output_asciiart": + if name == "dbl_nan": + return dbl_nan + elif name == "dbl_inf": + return dbl_inf + elif name == "output_asciiart": return output_asciiart elif name == "output_float64": return output_float @@ -86,6 +97,9 @@ def patch(module): module.Some = Some module.none = none + module.isnan = np.isnan + module.isinf = np.isinf + def file_import(filename, prefix="file_import_"): filename = pathlib.Path(filename) diff --git a/nac3standalone/demo/src/math.py b/nac3standalone/demo/src/math.py new file mode 100644 index 0000000..3d7b680 --- /dev/null +++ b/nac3standalone/demo/src/math.py @@ -0,0 +1,25 @@ +@extern +def output_bool(x: bool): + ... + +@extern +def dbl_nan() -> float: + ... + +@extern +def dbl_inf() -> float: + ... + +def test_isnan(): + for x in [dbl_nan(), 0.0, dbl_inf()]: + output_bool(isnan(x)) + +def test_isinf(): + for x in [dbl_inf(), 0.0, dbl_nan()]: + output_bool(isinf(x)) + +def run() -> int32: + test_isnan() + test_isinf() + + return 0 \ No newline at end of file -- 2.44.1 From 2b635a0b974adb5cdd72d9da26e725bbd620bd39 Mon Sep 17 00:00:00 2001 From: David Mak Date: Fri, 6 Oct 2023 17:48:31 +0800 Subject: [PATCH 07/10] core: Implement numpy and scipy functions --- nac3core/src/toplevel/builtins.rs | 463 ++++++++++++++++++++++---- nac3standalone/demo/interpret_demo.py | 42 +++ nac3standalone/demo/src/math.py | 205 ++++++++++++ 3 files changed, 646 insertions(+), 64 deletions(-) diff --git a/nac3core/src/toplevel/builtins.rs b/nac3core/src/toplevel/builtins.rs index 6f3488d..13db093 100644 --- a/nac3core/src/toplevel/builtins.rs +++ b/nac3core/src/toplevel/builtins.rs @@ -919,70 +919,22 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { )))), loc: None, })), - Arc::new(RwLock::new(TopLevelDef::Function { - name: "floor".into(), - simple_name: "floor".into(), - signature: primitives.1.add_ty(TypeEnum::TFunc(FunSignature { - args: vec![FuncArg { name: "n".into(), ty: float, default_value: None }], - ret: int32, - vars: Default::default(), - })), - var_id: Default::default(), - instance_to_symbol: Default::default(), - instance_to_stmt: Default::default(), - resolver: None, - codegen_callback: Some(Arc::new(GenCall::new(Box::new( - |ctx, _, _, args, generator| { - let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, ctx.primitives.float)?; - let floor_intrinsic = - ctx.module.get_function("llvm.floor.f64").unwrap_or_else(|| { - let float = ctx.ctx.f64_type(); - let fn_type = float.fn_type(&[float.into()], false); - ctx.module.add_function("llvm.floor.f64", fn_type, None) - }); - let val = ctx - .builder - .build_call(floor_intrinsic, &[arg.into()], "floor") - .try_as_basic_value() - .left() - .unwrap(); - Ok(val.into()) - }, - )))), - loc: None, - })), - Arc::new(RwLock::new(TopLevelDef::Function { - name: "ceil".into(), - simple_name: "ceil".into(), - signature: primitives.1.add_ty(TypeEnum::TFunc(FunSignature { - args: vec![FuncArg { name: "n".into(), ty: float, default_value: None }], - ret: int32, - vars: Default::default(), - })), - var_id: Default::default(), - instance_to_symbol: Default::default(), - instance_to_stmt: Default::default(), - resolver: None, - codegen_callback: Some(Arc::new(GenCall::new(Box::new( - |ctx, _, _, args, generator| { - let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, ctx.primitives.float)?; - let ceil_intrinsic = - ctx.module.get_function("llvm.ceil.f64").unwrap_or_else(|| { - let float = ctx.ctx.f64_type(); - let fn_type = float.fn_type(&[float.into()], false); - ctx.module.add_function("llvm.ceil.f64", fn_type, None) - }); - let val = ctx - .builder - .build_call(ceil_intrinsic, &[arg.into()], "ceil") - .try_as_basic_value() - .left() - .unwrap(); - Ok(val.into()) - }, - )))), - loc: None, - })), + create_fn_by_intrinsic( + primitives, + &var_map, + "floor", + float, + &[(float, "x")], + "llvm.floor.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "ceil", + float, + &[(float, "x")], + "llvm.ceil.f64", + ), Arc::new(RwLock::new({ let list_var = primitives.1.get_fresh_var(Some("L".into()), None); let list = primitives.1.add_ty(TypeEnum::TList { ty: list_var.0 }); @@ -1268,6 +1220,353 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { Ok(Some(val.into())) }), ), + create_fn_by_intrinsic( + primitives, + &var_map, + "sin", + float, + &[(float, "x")], + "llvm.sin.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "cos", + float, + &[(float, "x")], + "llvm.cos.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "exp", + float, + &[(float, "x")], + "llvm.exp.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "exp2", + float, + &[(float, "x")], + "llvm.exp2.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "log", + float, + &[(float, "x")], + "llvm.log.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "log10", + float, + &[(float, "x")], + "llvm.log10.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "log2", + float, + &[(float, "x")], + "llvm.log2.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "fabs", + float, + &[(float, "x")], + "llvm.fabs.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "trunc", + float, + &[(float, "x")], + "llvm.trunc.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "sqrt", + float, + &[(float, "x")], + "llvm.sqrt.f64", + ), + create_fn_by_codegen( + primitives, + &var_map, + "rint", + float, + &[(float, "x")], + Box::new(|ctx, _, fun, args, generator| { + let float = ctx.primitives.float; + let llvm_f64 = ctx.ctx.f64_type(); + + let x_ty = fun.0.args[0].ty; + let x_val = args[0].1.clone() + .to_basic_value_enum(ctx, generator, x_ty)?; + + assert!(ctx.unifier.unioned(x_ty, float)); + + let intrinsic_fn = ctx.module.get_function("llvm.round.f64").unwrap_or_else(|| { + let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false); + + ctx.module.add_function("llvm.round.f64", fn_type, None) + }); + + // rint(x) == round(x * 0.5) * 2.0 + + // %0 = fmul f64 %x, 0.5 + let x_half = ctx.builder + .build_float_mul(x_val.into_float_value(), llvm_f64.const_float(0.5), ""); + // %1 = call f64 @llvm.round.f64(f64 %0) + let round = ctx.builder + .build_call( + intrinsic_fn, + &vec![x_half.into()], + "", + ) + .try_as_basic_value() + .left() + .unwrap(); + // %2 = fmul f64 %1, 2.0 + let val = ctx.builder + .build_float_mul(round.into_float_value(), llvm_f64.const_float(2.0).into(), "rint"); + + Ok(Some(val.into())) + }), + ), + create_fn_by_extern( + primitives, + &var_map, + "tan", + float, + &[(float, "x")], + "tan", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "arcsin", + float, + &[(float, "x")], + "asin", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "arccos", + float, + &[(float, "x")], + "acos", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "arctan", + float, + &[(float, "x")], + "atan", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "sinh", + float, + &[(float, "x")], + "sinh", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "cosh", + float, + &[(float, "x")], + "cosh", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "tanh", + float, + &[(float, "x")], + "tanh", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "arcsinh", + float, + &[(float, "x")], + "asinh", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "arccosh", + float, + &[(float, "x")], + "acosh", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "arctanh", + float, + &[(float, "x")], + "atanh", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "expm1", + float, + &[(float, "x")], + "expm1", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "cbrt", + float, + &[(float, "x")], + "cbrt", + &["readnone", "willreturn"], + ), + create_fn_by_extern( + primitives, + &var_map, + "erf", + float, + &[(float, "z")], + "erf", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "erfc", + float, + &[(float, "x")], + "erfc", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "gamma", + float, + &[(float, "z")], + "tgamma", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "gammaln", + float, + &[(float, "x")], + "lgamma", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "j0", + float, + &[(float, "x")], + "j0", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "j1", + float, + &[(float, "x")], + "j1", + &[], + ), + // Not mapped: jv/yv, libm only supports integer orders. + create_fn_by_extern( + primitives, + &var_map, + "arctan2", + float, + &[(float, "x1"), (float, "x2")], + "atan2", + &[], + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "copysign", + float, + &[(float, "x1"), (float, "x2")], + "llvm.copysign.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "fmax", + float, + &[(float, "x1"), (float, "x2")], + "llvm.maxnum.f64", + ), + create_fn_by_intrinsic( + primitives, + &var_map, + "fmin", + float, + &[(float, "x1"), (float, "x2")], + "llvm.minnum.f64", + ), + create_fn_by_extern( + primitives, + &var_map, + "ldexp", + float, + &[(float, "x1"), (int32, "x2")], + "ldexp", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "hypot", + float, + &[(float, "x1"), (float, "x2")], + "hypot", + &[], + ), + create_fn_by_extern( + primitives, + &var_map, + "nextafter", + float, + &[(float, "x1"), (float, "x2")], + "nextafter", + &[], + ), Arc::new(RwLock::new(TopLevelDef::Function { name: "Some".into(), simple_name: "Some".into(), @@ -1314,6 +1613,42 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { "abs", "isnan", "isinf", + "sin", + "cos", + "exp", + "exp2", + "log", + "log10", + "log2", + "fabs", + "trunc", + "sqrt", + "rint", + "tan", + "arcsin", + "arccos", + "arctan", + "sinh", + "cosh", + "tanh", + "arcsinh", + "arccosh", + "arctanh", + "expm1", + "cbrt", + "erf", + "erfc", + "gamma", + "gammaln", + "j0", + "j1", + "arctan2", + "copysign", + "fmax", + "fmin", + "ldexp", + "hypot", + "nextafter", "Some", ], ) diff --git a/nac3standalone/demo/interpret_demo.py b/nac3standalone/demo/interpret_demo.py index 9c62e58..0339206 100755 --- a/nac3standalone/demo/interpret_demo.py +++ b/nac3standalone/demo/interpret_demo.py @@ -5,6 +5,7 @@ import importlib.util import importlib.machinery import numpy as np import pathlib +import scipy from numpy import int32, int64, uint32, uint64 from typing import TypeVar, Generic @@ -97,8 +98,49 @@ def patch(module): module.Some = Some module.none = none + # NumPy Math functions module.isnan = np.isnan module.isinf = np.isinf + module.sin = np.sin + module.cos = np.cos + module.exp = np.exp + module.exp2 = np.exp2 + module.log = np.log + module.log10 = np.log10 + module.log2 = np.log2 + module.fabs = np.fabs + module.floor = np.floor + module.ceil = np.ceil + module.trunc = np.trunc + module.sqrt = np.sqrt + module.rint = np.rint + module.tan = np.tan + module.arcsin = np.arcsin + module.arccos = np.arccos + module.arctan = np.arctan + module.sinh = np.sinh + module.cosh = np.cosh + module.tanh = np.tanh + module.arcsinh = np.arcsinh + module.arccosh = np.arccosh + module.arctanh = np.arctanh + module.expm1 = np.expm1 + module.cbrt = np.cbrt + module.arctan2 = np.arctan2 + module.copysign = np.copysign + module.fmax = np.fmax + module.fmin = np.fmin + module.ldexp = np.ldexp + module.hypot = np.hypot + module.nextafter = np.nextafter + + # SciPy Math Functions + module.erf = scipy.special.erf + module.erfc = scipy.special.erfc + module.gamma = scipy.special.gamma + module.gammaln = scipy.special.gammaln + module.j0 = scipy.special.j0 + module.j1 = scipy.special.j1 def file_import(filename, prefix="file_import_"): diff --git a/nac3standalone/demo/src/math.py b/nac3standalone/demo/src/math.py index 3d7b680..a55e3de 100644 --- a/nac3standalone/demo/src/math.py +++ b/nac3standalone/demo/src/math.py @@ -2,6 +2,10 @@ def output_bool(x: bool): ... +@extern +def output_float64(x: float): + ... + @extern def dbl_nan() -> float: ... @@ -18,8 +22,209 @@ def test_isinf(): for x in [dbl_inf(), 0.0, dbl_nan()]: output_bool(isinf(x)) +def test_sin(): + pi = 3.1415926535897932384626433 + for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi]: + output_float64(sin(x)) + +def test_cos(): + pi = 3.1415926535897932384626433 + for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi]: + output_float64(cos(x)) + +def test_exp(): + for x in [0.0, 1.0]: + output_float64(exp(x)) + +def test_exp2(): + for x in [0.0, 1.0]: + output_float64(exp2(x)) + +def test_log(): + e = 2.71828182845904523536028747135266249775724709369995 + for x in [1.0, e]: + output_float64(log(x)) + +def test_log10(): + for x in [1.0, 10.0]: + output_float64(log10(x)) + +def test_log2(): + for x in [1.0, 2.0]: + output_float64(log2(x)) + +def test_fabs(): + for x in [-1.0, 0.0, 1.0]: + output_float64(fabs(x)) + +def test_floor(): + for x in [-1.5, -0.5, 0.5, 1.5]: + output_float64(floor(x)) + +def test_ceil(): + for x in [-1.5, -0.5, 0.5, 1.5]: + output_float64(ceil(x)) + +def test_trunc(): + for x in [-1.5, -0.5, 0.5, 1.5]: + output_float64(trunc(x)) + +def test_sqrt(): + for x in [1.0, 2.0, 4.0]: + output_float64(sqrt(x)) + +def test_rint(): + for x in [-1.5, -0.5, 0.5, 1.5]: + output_float64(rint(x)) + +def test_tan(): + pi = 3.1415926535897932384626433 + for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi]: + output_float64(tan(x)) + +def test_arcsin(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(arcsin(x)) + +def test_arccos(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(arccos(x)) + +def test_arctan(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(arctan(x)) + +def test_sinh(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(sinh(x)) + +def test_cosh(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(cosh(x)) + +def test_tanh(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(tanh(x)) + +def test_arcsinh(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(arcsinh(x)) + +def test_arccosh(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(arccosh(x)) + +def test_arctanh(): + for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(arctanh(x)) + +def test_expm1(): + for x in [0.0, 1.0]: + output_float64(expm1(x)) + +def test_cbrt(): + for x in [1.0, 8.0, 27.0]: + output_float64(expm1(x)) + +def test_erf(): + for x in [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]: + output_float64(erf(x)) + +def test_erfc(): + for x in [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]: + output_float64(erfc(x)) + +def test_gamma(): + for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + output_float64(gamma(x)) + +def test_gammaln(): + for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + output_float64(gammaln(x)) + +def test_j0(): + for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + output_float64(j0(x)) + +def test_j1(): + for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + output_float64(j1(x)) + +def test_arctan2(): + for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(arctan2(x1, x2)) + +def test_copysign(): + for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(copysign(x1, x2)) + +def test_fmax(): + for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(fmax(x1, x2)) + +def test_fmin(): + for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + output_float64(fmin(x1, x2)) + +def test_ldexp(): + for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x2 in [-2, -1, 0, 1, 2]: + output_float64(ldexp(x1, x2)) + +def test_hypot(): + for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x2 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + output_float64(hypot(x1, x2)) + +def test_nextafter(): + for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x2 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + output_float64(nextafter(x1, x2)) + def run() -> int32: test_isnan() test_isinf() + test_sin() + test_cos() + test_exp() + test_exp2() + test_log() + test_log10() + test_log2() + test_fabs() + test_floor() + test_ceil() + test_trunc() + test_sqrt() + test_rint() + test_tan() + test_arcsin() + test_arccos() + test_arctan() + test_sinh() + test_cosh() + test_tanh() + test_arcsinh() + test_arccosh() + test_arctanh() + test_expm1() + test_cbrt() + test_erf() + test_erfc() + test_gamma() + test_gammaln() + test_j0() + test_j1() + test_arctan2() + test_copysign() + test_fmax() + test_fmin() + test_ldexp() + test_hypot() + test_nextafter() return 0 \ No newline at end of file -- 2.44.1 From 36a6a7b8cd5936f62dbba9f508926ec919a9834c Mon Sep 17 00:00:00 2001 From: David Mak Date: Tue, 10 Oct 2023 13:37:59 +0800 Subject: [PATCH 08/10] core: Replace TopLevelDef comments with documentation --- nac3core/src/toplevel/mod.rs | 44 ++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/nac3core/src/toplevel/mod.rs b/nac3core/src/toplevel/mod.rs index 643594d..5e5c7a7 100644 --- a/nac3core/src/toplevel/mod.rs +++ b/nac3core/src/toplevel/mod.rs @@ -82,51 +82,55 @@ pub struct FunInstance { #[derive(Debug, Clone)] pub enum TopLevelDef { Class { - // name for error messages and symbols + /// Name for error messages and symbols. name: StrRef, - // object ID used for TypeEnum + /// Object ID used for [TypeEnum]. object_id: DefinitionId, /// type variables bounded to the class. type_vars: Vec, - // class fields - // name, type, is mutable + /// Class fields. + /// + /// Name and type is mutable. fields: Vec<(StrRef, Type, bool)>, - // class methods, pointing to the corresponding function definition. + /// Class methods, pointing to the corresponding function definition. methods: Vec<(StrRef, Type, DefinitionId)>, - // ancestor classes, including itself. + /// Ancestor classes, including itself. ancestors: Vec, - // symbol resolver of the module defined the class, none if it is built-in type + /// Symbol resolver of the module defined the class; [None] if it is built-in type. resolver: Option>, - // constructor type + /// Constructor type. constructor: Option, - // definition location + /// Definition location. loc: Option, }, Function { - // prefix for symbol, should be unique globally + /// Prefix for symbol, should be unique globally. name: String, - // simple name, the same as in method/function definition + /// Simple name, the same as in method/function definition. simple_name: StrRef, - // function signature. + /// Function signature. signature: Type, - // instantiated type variable IDs + /// Instantiated type variable IDs. var_id: Vec, /// Function instance to symbol mapping - /// Key: string representation of type variable values, sorted by variable ID in ascending + /// + /// * Key: String representation of type variable values, sorted by variable ID in ascending /// order, including type variables associated with the class. - /// Value: function symbol name. + /// * Value: Function symbol name. instance_to_symbol: HashMap, /// Function instances to annotated AST mapping - /// Key: string representation of type variable values, sorted by variable ID in ascending + /// + /// * Key: String representation of type variable values, sorted by variable ID in ascending /// order, including type variables associated with the class. Excluding rigid type /// variables. - /// rigid type variables that would be substituted when the function is instantiated. + /// + /// Rigid type variables that would be substituted when the function is instantiated. instance_to_stmt: HashMap, - // symbol resolver of the module defined the class + /// Symbol resolver of the module defined the class. resolver: Option>, - // custom codegen callback + /// Custom code generation callback. codegen_callback: Option>, - // definition location + /// Definition location. loc: Option, }, } -- 2.44.1 From ff1fed112cd12240cfa20a782c37209b4706750c Mon Sep 17 00:00:00 2001 From: David Mak Date: Tue, 10 Oct 2023 18:19:36 +0800 Subject: [PATCH 09/10] core: Rework gamma/gammaln to match SciPy behavior Matches behavior for infinities and NaNs. --- nac3core/src/toplevel/builtins.rs | 190 ++++++++++++++++++++++++++++-- 1 file changed, 181 insertions(+), 9 deletions(-) diff --git a/nac3core/src/toplevel/builtins.rs b/nac3core/src/toplevel/builtins.rs index 13db093..7b2f9b6 100644 --- a/nac3core/src/toplevel/builtins.rs +++ b/nac3core/src/toplevel/builtins.rs @@ -1470,32 +1470,204 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo { "erfc", &[], ), - create_fn_by_extern( + create_fn_by_codegen( primitives, &var_map, "gamma", float, &[(float, "z")], - "tgamma", - &[], + Box::new(|ctx, _, fun, args, generator| { + let float = ctx.primitives.float; + let llvm_f64 = ctx.ctx.f64_type(); + + let z_ty = fun.0.args[0].ty; + let z_val = args[0].1.clone() + .to_basic_value_enum(ctx, generator, z_ty)?; + + assert!(ctx.unifier.unioned(z_ty, float)); + + let tgamma_fn = ctx.module.get_function("tgamma").unwrap_or_else(|| { + let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false); + let func = ctx.module.add_function("tgamma", fn_type, None); + func.add_attribute( + AttributeLoc::Function, + ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id("nounwind"), 0) + ); + + func + }); + + // %0 = call f64 @tgamma(f64 %z) + let call = ctx.builder + .build_call(tgamma_fn, &[z_val.into()], "gamma") + .try_as_basic_value() + .left() + .unwrap() + .into_float_value(); + + // Handling for denormals + // | x | Python gamma(x) | C tgamma(x) | + // --- | ----------------- | --------------- | ----------- | + // (1) | nan | nan | nan | + // (2) | -inf | -inf | inf | + // (3) | inf | inf | inf | + // (4) | 0.0 | inf | inf | + // (5) | {-1.0, -2.0, ...} | inf | nan | + // + // Therefore, we remap to Python's denorm handling by: + // + // let v = tgamma(x); + // v = if isinf(v) || isnan(v) { f64::INFINITY } else { v } // Handles (4)-(5) + // v = if isinf(x) || isnan(x) { x } else { v } // Handles (1)-(3) + + // %v.isinf = call i32 @__nac3_isinf(f64 %0) + // %v.isinf.tobool = icmp ne i32 %v.isinf, 0 + let v_isinf = call_isinf(generator, ctx, call.into()); + // %v.isnan = call i32 @__nac3_isnan(f64 %0) + // %v.isnan.tobool = icmp ne i32 %v.isnan, 0 + let v_isnan = call_isnan(generator, ctx, call.into()); + + // %or = or i1 %v.isinf.tobool, %v.isnan.tobool + // %3 = select i1 %or, f64 inf, f64 %0 + let v_is_nonnum = ctx.builder.build_or(v_isinf, v_isnan, ""); + let val = ctx.builder.build_select( + v_is_nonnum, + llvm_f64.const_float(f64::INFINITY).into(), + call, + "", + ).into_float_value(); + + // %z.isinf = call i32 @__nac3_isinf(f64 %z) + // %z.isinf.tobool = icmp ne i32 %z.isinf, 0 + let z_isinf = call_isinf(generator, ctx, z_val.into_float_value()); + // %z.isnan = call i32 @__nac3_isnan(f64 %z) + // %z.isnan.tobool = icmp ne i32 %z.isnan, 0 + let z_isnan = call_isnan(generator, ctx, z_val.into_float_value()); + + // %or = or i1 %z.isinf.tobool, %z.isnan.tobool + // %val = select i1 %or, f64 %z, f64 %3 + let z_is_nonnum = ctx.builder.build_or(z_isinf, z_isnan, ""); + let val = ctx.builder.build_select( + z_is_nonnum, + z_val.into_float_value(), + val, + "", + ); + + Ok(val.into()) + }), ), - create_fn_by_extern( + create_fn_by_codegen( primitives, &var_map, "gammaln", float, &[(float, "x")], - "lgamma", - &[], + Box::new(|ctx, _, fun, args, generator| { + let float = ctx.primitives.float; + let llvm_f64 = ctx.ctx.f64_type(); + + let x_ty = fun.0.args[0].ty; + let x_val = args[0].1.clone() + .to_basic_value_enum(ctx, generator, x_ty)?; + + assert!(ctx.unifier.unioned(x_ty, float)); + + let tgamma_fn = ctx.module.get_function("lgamma").unwrap_or_else(|| { + let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false); + let func = ctx.module.add_function("lgamma", fn_type, None); + func.add_attribute( + AttributeLoc::Function, + ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id("nounwind"), 0) + ); + + func + }); + + // %0 = call f64 @gamma(f64 %x) + let call = ctx.builder + .build_call(tgamma_fn, &[x_val.into()], "gammaln") + .try_as_basic_value() + .left() + .unwrap() + .into_float_value(); + + // libm's handling of value overflows differs from scipy: + // - scipy: gammaln(-inf) -> -inf + // - libm : lgamma(-inf) -> inf + // + // Therefore we remap it by: + // + // let v = lgamma(x); + // v = if isinf(x) { x } else { v } + + // %isinf = call i32 @__nac3_isinf(f64 %x) + // %tobool = icmp ne i32 %isinf, 0 + // %val = select i1 %tobool, f64 %x, f64 %0 + let v = ctx.builder.build_select( + call_isinf(generator, ctx, x_val.into_float_value()), + x_val, + call.into(), + "" + ); + + Ok(v.into()) + }), ), - create_fn_by_extern( + create_fn_by_codegen( primitives, &var_map, "j0", float, &[(float, "x")], - "j0", - &[], + Box::new(|ctx, _, fun, args, generator| { + let float = ctx.primitives.float; + let llvm_f64 = ctx.ctx.f64_type(); + + let x_ty = fun.0.args[0].ty; + let x_val = args[0].1.clone() + .to_basic_value_enum(ctx, generator, x_ty)?; + + assert!(ctx.unifier.unioned(x_ty, float)); + + let tgamma_fn = ctx.module.get_function("j0").unwrap_or_else(|| { + let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false); + let func = ctx.module.add_function("j0", fn_type, None); + func.add_attribute( + AttributeLoc::Function, + ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id("nounwind"), 0) + ); + + func + }); + + // %0 = call f64 @j0(f64 %x) + let call = ctx.builder + .build_call(tgamma_fn, &[x_val.into()], "j0") + .try_as_basic_value() + .left() + .unwrap() + .into_float_value(); + + // libm's handling of value overflows differs from scipy: + // - scipy: j0(inf) -> nan + // - libm : j0(inf) -> 0.0 + // + // Therefore we remap it by: + // + // let v = j0(x); + // v = if isinf(x) { f64::NAN } else { v } + + // %1 = call i32 @__nac3_isinf(f64 %x) + // %tobool = icmp ne i32 %isinf, 0 + let arg_isinf = call_isinf(generator, ctx, x_val.into_float_value()); + + // %val = select i1 %tobool, f64 nan, f64 %0 + let val = ctx.builder + .build_select(arg_isinf, llvm_f64.const_float(f64::NAN), call, ""); + + Ok(val.into()) + }), ), create_fn_by_extern( primitives, -- 2.44.1 From 7e4dab15ae5c02db0c452bcbd22538918821e732 Mon Sep 17 00:00:00 2001 From: David Mak Date: Wed, 11 Oct 2023 17:52:12 +0800 Subject: [PATCH 10/10] standalone: Add math tests for non-number arguments --- nac3standalone/demo/src/math.py | 102 +++++++++++++++++--------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/nac3standalone/demo/src/math.py b/nac3standalone/demo/src/math.py index a55e3de..9fa3951 100644 --- a/nac3standalone/demo/src/math.py +++ b/nac3standalone/demo/src/math.py @@ -14,136 +14,142 @@ def dbl_nan() -> float: def dbl_inf() -> float: ... +def dbl_pi() -> float: + return 3.1415926535897932384626433 + +def dbl_e() -> float: + return 2.71828182845904523536028747135266249775724709369995 + def test_isnan(): for x in [dbl_nan(), 0.0, dbl_inf()]: output_bool(isnan(x)) def test_isinf(): - for x in [dbl_inf(), 0.0, dbl_nan()]: + for x in [dbl_inf(), -dbl_inf(), 0.0, dbl_nan()]: output_bool(isinf(x)) def test_sin(): - pi = 3.1415926535897932384626433 - for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi]: + pi = dbl_pi() + for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(sin(x)) def test_cos(): - pi = 3.1415926535897932384626433 - for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi]: + pi = dbl_pi() + for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(cos(x)) def test_exp(): - for x in [0.0, 1.0]: + for x in [0.0, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(exp(x)) def test_exp2(): - for x in [0.0, 1.0]: + for x in [0.0, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(exp2(x)) def test_log(): - e = 2.71828182845904523536028747135266249775724709369995 - for x in [1.0, e]: + e = dbl_e() + for x in [1.0, e, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(log(x)) def test_log10(): - for x in [1.0, 10.0]: + for x in [1.0, 10.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(log10(x)) def test_log2(): - for x in [1.0, 2.0]: + for x in [1.0, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(log2(x)) def test_fabs(): - for x in [-1.0, 0.0, 1.0]: + for x in [-1.0, 0.0, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(fabs(x)) def test_floor(): - for x in [-1.5, -0.5, 0.5, 1.5]: + for x in [-1.5, -0.5, 0.5, 1.5, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(floor(x)) def test_ceil(): - for x in [-1.5, -0.5, 0.5, 1.5]: + for x in [-1.5, -0.5, 0.5, 1.5, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(ceil(x)) def test_trunc(): - for x in [-1.5, -0.5, 0.5, 1.5]: + for x in [-1.5, -0.5, 0.5, 1.5, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(trunc(x)) def test_sqrt(): - for x in [1.0, 2.0, 4.0]: + for x in [1.0, 2.0, 4.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(sqrt(x)) def test_rint(): - for x in [-1.5, -0.5, 0.5, 1.5]: + for x in [-1.5, -0.5, 0.5, 1.5, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(rint(x)) def test_tan(): - pi = 3.1415926535897932384626433 - for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi]: + pi = dbl_pi() + for x in [-pi, -pi / 2.0, -pi / 4.0, 0.0, pi / 4.0, pi / 2.0, pi, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(tan(x)) def test_arcsin(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(arcsin(x)) def test_arccos(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(arccos(x)) def test_arctan(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(arctan(x)) def test_sinh(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(sinh(x)) def test_cosh(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(cosh(x)) def test_tanh(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(tanh(x)) def test_arcsinh(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(arcsinh(x)) def test_arccosh(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(arccosh(x)) def test_arctanh(): - for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(arctanh(x)) def test_expm1(): - for x in [0.0, 1.0]: + for x in [0.0, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(expm1(x)) def test_cbrt(): - for x in [1.0, 8.0, 27.0]: + for x in [1.0, 8.0, 27.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(expm1(x)) def test_erf(): - for x in [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]: + for x in [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(erf(x)) def test_erfc(): - for x in [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]: + for x in [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(erfc(x)) def test_gamma(): - for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(gamma(x)) def test_gammaln(): - for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(gammaln(x)) def test_j0(): - for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(j0(x)) def test_j1(): @@ -151,38 +157,38 @@ def test_j1(): output_float64(j1(x)) def test_arctan2(): - for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0]: - for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: + for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(arctan2(x1, x2)) def test_copysign(): - for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0]: - for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: + for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(copysign(x1, x2)) def test_fmax(): - for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0]: - for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: + for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(fmax(x1, x2)) def test_fmin(): - for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0]: - for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0]: + for x1 in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: + for x2 in [-1.0, -0.5, 0.0, 0.5, 1.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(fmin(x1, x2)) def test_ldexp(): - for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: for x2 in [-2, -1, 0, 1, 2]: output_float64(ldexp(x1, x2)) def test_hypot(): - for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: - for x2 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: + for x2 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(hypot(x1, x2)) def test_nextafter(): - for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: - for x2 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]: + for x1 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: + for x2 in [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, dbl_inf(), -dbl_inf(), dbl_nan()]: output_float64(nextafter(x1, x2)) def run() -> int32: -- 2.44.1