From d3e7daddbf8c65c4b7f71bb59d953eb6da90b49d Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Mon, 20 Mar 2023 14:53:39 -0400 Subject: [PATCH 1/3] Add currently broken example --- test/integration/good/code-gen/cpp.expected | 346 ++++++++++++++++++ .../good/code-gen/funcall-type-promotion.stan | 19 + 2 files changed, 365 insertions(+) create mode 100644 test/integration/good/code-gen/funcall-type-promotion.stan diff --git a/test/integration/good/code-gen/cpp.expected b/test/integration/good/code-gen/cpp.expected index d710fdc35e..9b7f043109 100644 --- a/test/integration/good/code-gen/cpp.expected +++ b/test/integration/good/code-gen/cpp.expected @@ -2631,6 +2631,352 @@ new_model(stan::io::var_context& data_context, unsigned int seed, stan::math::profile_map& get_stan_profile_data() { return eight_schools_ncp_model_namespace::profiles__; } +#endif + $ ../../../../../install/default/bin/stanc --print-cpp funcall-type-promotion.stan +// Code generated by %%NAME%% %%VERSION%% +#include +namespace funcall_type_promotion_model_namespace { +using stan::model::model_base_crtp; +using namespace stan::math; +stan::math::profile_map profiles__; +static constexpr std::array locations_array__ = + {" (found before start of program)", + " (in 'funcall-type-promotion.stan', line 16, column 2 to column 22)", + " (in 'funcall-type-promotion.stan', line 17, column 2 to column 21)", + " (in 'funcall-type-promotion.stan', line 18, column 2 to column 12)", + " (in 'funcall-type-promotion.stan', line 11, column 2 to column 21)", + " (in 'funcall-type-promotion.stan', line 12, column 2 to column 19)", + " (in 'funcall-type-promotion.stan', line 13, column 2 to column 12)", + " (in 'funcall-type-promotion.stan', line 3, column 4 to column 17)", + " (in 'funcall-type-promotion.stan', line 2, column 27 to line 4, column 3)", + " (in 'funcall-type-promotion.stan', line 7, column 4 to column 17)", + " (in 'funcall-type-promotion.stan', line 6, column 27 to line 8, column 3)"}; +template , + stan::is_stan_scalar>* = nullptr> +stan::promote_args_t +foo(const T0__& x, const T1__& y, std::ostream* pstream__); +template , + stan::is_stan_scalar>* = nullptr> +void bar(const T0__& x, const T1__& y, std::ostream* pstream__); +template , + stan::is_stan_scalar>*> +stan::promote_args_t +foo(const T0__& x, const T1__& y, std::ostream* pstream__) { + using local_scalar_t__ = stan::promote_args_t; + int current_statement__ = 0; + static constexpr bool propto__ = true; + // suppress unused var warning + (void) propto__; + local_scalar_t__ DUMMY_VAR__(std::numeric_limits::quiet_NaN()); + // suppress unused var warning + (void) DUMMY_VAR__; + try { + current_statement__ = 7; + return (x / y); + } catch (const std::exception& e) { + stan::lang::rethrow_located(e, locations_array__[current_statement__]); + } +} +template , + stan::is_stan_scalar>*> +void bar(const T0__& x, const T1__& y, std::ostream* pstream__) { + using local_scalar_t__ = stan::promote_args_t; + int current_statement__ = 0; + static constexpr bool propto__ = true; + // suppress unused var warning + (void) propto__; + local_scalar_t__ DUMMY_VAR__(std::numeric_limits::quiet_NaN()); + // suppress unused var warning + (void) DUMMY_VAR__; + try { + current_statement__ = 9; + if (pstream__) { + stan::math::stan_print(pstream__, (x / y)); + stan::math::stan_print(pstream__, "\n"); + } + } catch (const std::exception& e) { + stan::lang::rethrow_located(e, locations_array__[current_statement__]); + } +} +class funcall_type_promotion_model final : public model_base_crtp { + private: + double x; + public: + ~funcall_type_promotion_model() {} + funcall_type_promotion_model(stan::io::var_context& context__, unsigned int + random_seed__ = 0, std::ostream* + pstream__ = nullptr) : model_base_crtp(0) { + int current_statement__ = 0; + using local_scalar_t__ = double; + boost::ecuyer1988 base_rng__ = + stan::services::util::create_rng(random_seed__, 0); + // suppress unused var warning + (void) base_rng__; + static constexpr const char* function__ = + "funcall_type_promotion_model_namespace::funcall_type_promotion_model"; + // suppress unused var warning + (void) function__; + local_scalar_t__ DUMMY_VAR__(std::numeric_limits::quiet_NaN()); + // suppress unused var warning + (void) DUMMY_VAR__; + try { + int pos__ = std::numeric_limits::min(); + pos__ = 1; + current_statement__ = 4; + x = std::numeric_limits::quiet_NaN(); + current_statement__ = 4; + x = foo(1, 2, pstream__); + current_statement__ = 5; + if (pstream__) { + stan::math::stan_print(pstream__, "x = "); + stan::math::stan_print(pstream__, x); + stan::math::stan_print(pstream__, "\n"); + } + current_statement__ = 6; + bar(1, 2, pstream__); + } catch (const std::exception& e) { + stan::lang::rethrow_located(e, locations_array__[current_statement__]); + } + num_params_r__ = 0U; + } + inline std::string model_name() const final { + return "funcall_type_promotion_model"; + } + inline std::vector model_compile_info() const noexcept { + return std::vector{"stanc_version = %%NAME%%3 %%VERSION%%", + "stancflags = --print-cpp"}; + } + template * = nullptr, + stan::require_vector_like_vt* = nullptr> + inline stan::scalar_type_t + log_prob_impl(VecR& params_r__, VecI& params_i__, std::ostream* + pstream__ = nullptr) const { + using T__ = stan::scalar_type_t; + using local_scalar_t__ = T__; + T__ lp__(0.0); + stan::math::accumulator lp_accum__; + stan::io::deserializer in__(params_r__, params_i__); + int current_statement__ = 0; + local_scalar_t__ DUMMY_VAR__(std::numeric_limits::quiet_NaN()); + // suppress unused var warning + (void) DUMMY_VAR__; + static constexpr const char* function__ = + "funcall_type_promotion_model_namespace::log_prob"; + // suppress unused var warning + (void) function__; + try { + local_scalar_t__ x2 = DUMMY_VAR__; + current_statement__ = 1; + x2 = foo(1, 2, pstream__); + current_statement__ = 2; + if (pstream__) { + stan::math::stan_print(pstream__, "x2 = "); + stan::math::stan_print(pstream__, x2); + stan::math::stan_print(pstream__, "\n"); + } + current_statement__ = 3; + bar(1, 2, pstream__); + } catch (const std::exception& e) { + stan::lang::rethrow_located(e, locations_array__[current_statement__]); + } + lp_accum__.add(lp__); + return lp_accum__.sum(); + } + template * = nullptr, stan::require_vector_like_vt* = nullptr, stan::require_vector_vt* = nullptr> + inline void + write_array_impl(RNG& base_rng__, VecR& params_r__, VecI& params_i__, + VecVar& vars__, const bool + emit_transformed_parameters__ = true, const bool + emit_generated_quantities__ = true, std::ostream* + pstream__ = nullptr) const { + using local_scalar_t__ = double; + stan::io::deserializer in__(params_r__, params_i__); + stan::io::serializer out__(vars__); + static constexpr bool propto__ = true; + // suppress unused var warning + (void) propto__; + double lp__ = 0.0; + // suppress unused var warning + (void) lp__; + int current_statement__ = 0; + stan::math::accumulator lp_accum__; + local_scalar_t__ DUMMY_VAR__(std::numeric_limits::quiet_NaN()); + // suppress unused var warning + (void) DUMMY_VAR__; + constexpr bool jacobian__ = false; + static constexpr const char* function__ = + "funcall_type_promotion_model_namespace::write_array"; + // suppress unused var warning + (void) function__; + try { + if (stan::math::logical_negation( + (stan::math::primitive_value(emit_transformed_parameters__) || + stan::math::primitive_value(emit_generated_quantities__)))) { + return ; + } + if (stan::math::logical_negation(emit_generated_quantities__)) { + return ; + } + } catch (const std::exception& e) { + stan::lang::rethrow_located(e, locations_array__[current_statement__]); + } + } + template * = nullptr, + stan::require_vector_like_vt* = nullptr> + inline void + transform_inits_impl(VecVar& params_r__, VecI& params_i__, VecVar& vars__, + std::ostream* pstream__ = nullptr) const { + using local_scalar_t__ = double; + stan::io::deserializer in__(params_r__, params_i__); + stan::io::serializer out__(vars__); + int current_statement__ = 0; + local_scalar_t__ DUMMY_VAR__(std::numeric_limits::quiet_NaN()); + // suppress unused var warning + (void) DUMMY_VAR__; + try { + int pos__ = std::numeric_limits::min(); + pos__ = 1; + } catch (const std::exception& e) { + stan::lang::rethrow_located(e, locations_array__[current_statement__]); + } + } + inline void + get_param_names(std::vector& names__, const bool + emit_transformed_parameters__ = true, const bool + emit_generated_quantities__ = true) const { + names__ = std::vector{}; + if (emit_transformed_parameters__) {} + if (emit_generated_quantities__) {} + } + inline void + get_dims(std::vector>& dimss__, const bool + emit_transformed_parameters__ = true, const bool + emit_generated_quantities__ = true) const { + dimss__ = std::vector>{}; + if (emit_transformed_parameters__) {} + if (emit_generated_quantities__) {} + } + inline void + constrained_param_names(std::vector& param_names__, bool + emit_transformed_parameters__ = true, bool + emit_generated_quantities__ = true) const final { + if (emit_transformed_parameters__) {} + if (emit_generated_quantities__) {} + } + inline void + unconstrained_param_names(std::vector& param_names__, bool + emit_transformed_parameters__ = true, bool + emit_generated_quantities__ = true) const final { + if (emit_transformed_parameters__) {} + if (emit_generated_quantities__) {} + } + inline std::string get_constrained_sizedtypes() const { + return std::string("[]"); + } + inline std::string get_unconstrained_sizedtypes() const { + return std::string("[]"); + } + // Begin method overload boilerplate + template inline void + write_array(RNG& base_rng, Eigen::Matrix& params_r, + Eigen::Matrix& vars, const bool + emit_transformed_parameters = true, const bool + emit_generated_quantities = true, std::ostream* + pstream = nullptr) const { + const size_t num_params__ = 0; + const size_t num_transformed = emit_transformed_parameters * (0); + const size_t num_gen_quantities = emit_generated_quantities * (0); + const size_t num_to_write = num_params__ + num_transformed + + num_gen_quantities; + std::vector params_i; + vars = Eigen::Matrix::Constant(num_to_write, + std::numeric_limits::quiet_NaN()); + write_array_impl(base_rng, params_r, params_i, vars, + emit_transformed_parameters, emit_generated_quantities, pstream); + } + template inline void + write_array(RNG& base_rng, std::vector& params_r, std::vector& + params_i, std::vector& vars, bool + emit_transformed_parameters = true, bool + emit_generated_quantities = true, std::ostream* + pstream = nullptr) const { + const size_t num_params__ = 0; + const size_t num_transformed = emit_transformed_parameters * (0); + const size_t num_gen_quantities = emit_generated_quantities * (0); + const size_t num_to_write = num_params__ + num_transformed + + num_gen_quantities; + vars = std::vector(num_to_write, + std::numeric_limits::quiet_NaN()); + write_array_impl(base_rng, params_r, params_i, vars, + emit_transformed_parameters, emit_generated_quantities, pstream); + } + template inline T_ + log_prob(Eigen::Matrix& params_r, std::ostream* pstream = nullptr) const { + Eigen::Matrix params_i; + return log_prob_impl(params_r, params_i, pstream); + } + template inline T_ + log_prob(std::vector& params_r, std::vector& params_i, + std::ostream* pstream = nullptr) const { + return log_prob_impl(params_r, params_i, pstream); + } + inline void + transform_inits(const stan::io::var_context& context, + Eigen::Matrix& params_r, std::ostream* + pstream = nullptr) const final { + std::vector params_r_vec(params_r.size()); + std::vector params_i; + transform_inits(context, params_i, params_r_vec, pstream); + params_r = Eigen::Map>(params_r_vec.data(), + params_r_vec.size()); + } + inline void + transform_inits(const stan::io::var_context& context, std::vector& + params_i, std::vector& vars, std::ostream* + pstream__ = nullptr) const { + constexpr std::array names__{}; + const std::array constrain_param_sizes__{}; + const auto num_constrained_params__ = + std::accumulate(constrain_param_sizes__.begin(), + constrain_param_sizes__.end(), 0); + std::vector params_r_flat__(num_constrained_params__); + Eigen::Index size_iter__ = 0; + Eigen::Index flat_iter__ = 0; + for (auto&& param_name__: names__) { + const auto param_vec__ = context.vals_r(param_name__); + for (Eigen::Index i = 0; i < constrain_param_sizes__[size_iter__]; ++i) { + params_r_flat__[flat_iter__] = param_vec__[i]; + ++flat_iter__; + } + ++size_iter__; + } + vars.resize(num_params_r__); + transform_inits_impl(params_r_flat__, params_i, vars, pstream__); + } +}; +} +using stan_model = funcall_type_promotion_model_namespace::funcall_type_promotion_model; +#ifndef USING_R +// Boilerplate +stan::model::model_base& +new_model(stan::io::var_context& data_context, unsigned int seed, + std::ostream* msg_stream) { + stan_model* m = new stan_model(data_context, seed, msg_stream); + return *m; +} +stan::math::profile_map& get_stan_profile_data() { + return funcall_type_promotion_model_namespace::profiles__; +} #endif $ ../../../../../install/default/bin/stanc --print-cpp mixed_type_arrays.stan // Code generated by %%NAME%% %%VERSION%% diff --git a/test/integration/good/code-gen/funcall-type-promotion.stan b/test/integration/good/code-gen/funcall-type-promotion.stan new file mode 100644 index 0000000000..4ec772c4e5 --- /dev/null +++ b/test/integration/good/code-gen/funcall-type-promotion.stan @@ -0,0 +1,19 @@ +functions { + real foo(real x, real y) { + return x / y; + } + + void bar(real x, real y) { + print(x / y); + } +} +transformed data { + real x = foo(1, 2); + print("x = ", x); + bar(1, 2); +} +model { + real x2 = foo(1, 2); + print("x2 = ", x2); + bar(1, 2); +} From 8e5f9cd33628df1d5bd7ad6264639bbde72b0a95 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Mon, 20 Mar 2023 14:54:50 -0400 Subject: [PATCH 2/3] Static cast ints to reals when needed for UDF calls --- src/stan_math_backend/Cpp.ml | 2 ++ src/stan_math_backend/Lower_expr.ml | 15 +++++++++++---- src/stan_math_backend/Lower_program.ml | 7 ++----- test/integration/good/code-gen/cpp.expected | 14 +++++++------- .../good/compiler-optimizations/cppO0.expected | 6 ++++-- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/stan_math_backend/Cpp.ml b/src/stan_math_backend/Cpp.ml index 68a894fd29..9680b8aa56 100644 --- a/src/stan_math_backend/Cpp.ml +++ b/src/stan_math_backend/Cpp.ml @@ -119,6 +119,8 @@ module Exprs = struct (** Helper for [std::numeric_limits::min()] *) let int_min = fun_call "std::numeric_limits::min" [] + + let static_cast type_ expr = FunCall ("static_cast", [type_], [expr]) end module Expression_syntax = struct diff --git a/src/stan_math_backend/Lower_expr.ml b/src/stan_math_backend/Lower_expr.ml index 0d0b763948..df9bd1695b 100644 --- a/src/stan_math_backend/Lower_expr.ml +++ b/src/stan_math_backend/Lower_expr.ml @@ -382,7 +382,7 @@ and lower_user_defined_fun f suffix es = let extra_args = suffix_args suffix @ ["pstream__"] |> List.map ~f:Exprs.to_var in Exprs.templated_fun_call f (templates true suffix) - (lower_exprs es @ extra_args) + ((lower_exprs ~promote_reals:true) es @ extra_args) and lower_compiler_internal ad ut f es = let open Expression_syntax in @@ -483,7 +483,8 @@ and lower_indexed_simple e idcs = List.fold idcs ~init:(lower_expr e) ~f:(fun e id -> Index (e, idx_minus_one id) ) -and lower_expr (Expr.Fixed.{pattern; meta} : Expr.Typed.t) : Cpp.expr = +and lower_expr ?(promote_reals = false) + (Expr.Fixed.{pattern; meta} : Expr.Typed.t) : Cpp.expr = let open Exprs in match pattern with | Var s -> Var s @@ -491,7 +492,12 @@ and lower_expr (Expr.Fixed.{pattern; meta} : Expr.Typed.t) : Cpp.expr = | Lit (Imaginary, s) -> fun_call "stan::math::to_complex" [Literal "0"; Literal s] | Lit ((Real | Int), s) -> Literal s - | Promotion (expr, UReal, _) when is_scalar expr -> lower_expr expr + | Promotion (expr, UReal, _) when is_scalar expr -> + if promote_reals then + (* this can be important for e.g. templated function calls + where we might generate an incorrect specification for int *) + static_cast Cpp.Double ((lower_expr ~promote_reals:true) expr) + else lower_expr expr | Promotion (expr, UComplex, DataOnly) when is_scalar expr -> (* this is in principle a little better than promote_scalar since it is constexpr *) fun_call "stan::math::to_complex" [lower_expr expr; Literal "0"] @@ -538,7 +544,8 @@ and lower_expr (Expr.Fixed.{pattern; meta} : Expr.Typed.t) : Cpp.expr = lower_indexed_simple e idx | _ -> lower_indexed e idx (Fmt.to_to_string Expr.Typed.pp e) ) -and lower_exprs = List.map ~f:lower_expr +and lower_exprs ?(promote_reals = false) = + List.map ~f:(lower_expr ~promote_reals) module Testing = struct (* these functions are just for testing *) diff --git a/src/stan_math_backend/Lower_program.ml b/src/stan_math_backend/Lower_program.ml index 2afafe58f3..8601877146 100644 --- a/src/stan_math_backend/Lower_program.ml +++ b/src/stan_math_backend/Lower_program.ml @@ -105,9 +105,7 @@ let gen_validate_data name st = if String.is_suffix ~suffix:"__" name then [] else let vector args = - let cast x = - Exprs.templated_fun_call "static_cast" [Types.size_t] [lower_expr x] - in + let cast x = Exprs.static_cast Types.size_t (lower_expr x) in InitializerExpr (Types.std_vector Types.size_t, List.map ~f:cast args) in let open Expression_syntax in @@ -344,8 +342,7 @@ let gen_get_param_names {Program.output_vars; _} = ~body ~cv_qualifiers:[Const] () ) let gen_get_dims {Program.output_vars; _} = - let cast x = - Exprs.templated_fun_call "static_cast" [Types.size_t] [lower_expr x] in + let cast x = Exprs.static_cast Types.size_t (lower_expr x) in let pack inner_dims = Exprs.std_vector_init_expr Types.size_t (List.map ~f:cast (SizedType.get_dims_io inner_dims)) in diff --git a/test/integration/good/code-gen/cpp.expected b/test/integration/good/code-gen/cpp.expected index 9b7f043109..c2917b9f46 100644 --- a/test/integration/good/code-gen/cpp.expected +++ b/test/integration/good/code-gen/cpp.expected @@ -2729,7 +2729,7 @@ class funcall_type_promotion_model final : public model_base_crtp::quiet_NaN(); current_statement__ = 4; - x = foo(1, 2, pstream__); + x = foo(static_cast(1), static_cast(2), pstream__); current_statement__ = 5; if (pstream__) { stan::math::stan_print(pstream__, "x = "); @@ -2737,7 +2737,7 @@ class funcall_type_promotion_model final : public model_base_crtp(1), static_cast(2), pstream__); } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); } @@ -2772,7 +2772,7 @@ class funcall_type_promotion_model final : public model_base_crtp(1), static_cast(2), pstream__); current_statement__ = 2; if (pstream__) { stan::math::stan_print(pstream__, "x2 = "); @@ -2780,7 +2780,7 @@ class funcall_type_promotion_model final : public model_base_crtp(1), static_cast(2), pstream__); } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); } @@ -12353,7 +12353,7 @@ class motherHOF_model final : public model_base_crtp { abc2_p = map_rectfake(abc1_p, pstream__); local_scalar_t__ abc3_p = DUMMY_VAR__; current_statement__ = 10; - abc3_p = map_rectfake(12, pstream__); + abc3_p = map_rectfake(static_cast(12), pstream__); Eigen::Matrix y_hat_tp1 = Eigen::Matrix::Constant(3, DUMMY_VAR__); current_statement__ = 11; @@ -12717,7 +12717,7 @@ class motherHOF_model final : public model_base_crtp { current_statement__ = 9; abc2_p = map_rectfake(abc1_p, pstream__); current_statement__ = 10; - abc3_p = map_rectfake(12, pstream__); + abc3_p = map_rectfake(static_cast(12), pstream__); current_statement__ = 11; stan::model::assign(y_hat_tp1, stan::math::map_rect<1, foo_functor__>(shared_params_p, job_params_d, @@ -12924,7 +12924,7 @@ class motherHOF_model final : public model_base_crtp { x_d_r, x_d_i, pstream__, 1e-8); double abc1_gq = std::numeric_limits::quiet_NaN(); current_statement__ = 61; - abc1_gq = map_rectfake(12, pstream__); + abc1_gq = map_rectfake(static_cast(12), pstream__); double abc2_gq = std::numeric_limits::quiet_NaN(); current_statement__ = 62; abc2_gq = map_rectfake(abc1_p, pstream__); diff --git a/test/integration/good/compiler-optimizations/cppO0.expected b/test/integration/good/compiler-optimizations/cppO0.expected index c39db5e1ba..694636ef29 100644 --- a/test/integration/good/compiler-optimizations/cppO0.expected +++ b/test/integration/good/compiler-optimizations/cppO0.expected @@ -17986,7 +17986,8 @@ class optimizations_model final : public model_base_crtp { { local_scalar_t__ x = DUMMY_VAR__; current_statement__ = 7; - nrfun_lp(4, 3, lp__, lp_accum__, pstream__); + nrfun_lp(static_cast(4), 3, lp__, lp_accum__, + pstream__); current_statement__ = 8; if (pstream__) { stan::math::stan_print(pstream__, rfun(3, pstream__)); @@ -18005,7 +18006,8 @@ class optimizations_model final : public model_base_crtp { current_statement__ = 11; lp_accum__.add(rfun(8, pstream__)); current_statement__ = 12; - nrfun_lp(34, 3, lp__, lp_accum__, pstream__); + nrfun_lp(static_cast(34), 3, lp__, lp_accum__, + pstream__); } current_statement__ = 17; for (int i = 1; i <= 5; ++i) { From 0dd667d2f080c1c2fd50dda66a4fa57138710323 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Mon, 20 Mar 2023 15:57:40 -0400 Subject: [PATCH 3/3] Don't pass promote_reals recursively --- src/stan_math_backend/Lower_expr.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stan_math_backend/Lower_expr.ml b/src/stan_math_backend/Lower_expr.ml index df9bd1695b..0333b9ddff 100644 --- a/src/stan_math_backend/Lower_expr.ml +++ b/src/stan_math_backend/Lower_expr.ml @@ -496,7 +496,7 @@ and lower_expr ?(promote_reals = false) if promote_reals then (* this can be important for e.g. templated function calls where we might generate an incorrect specification for int *) - static_cast Cpp.Double ((lower_expr ~promote_reals:true) expr) + static_cast Cpp.Double (lower_expr expr) else lower_expr expr | Promotion (expr, UComplex, DataOnly) when is_scalar expr -> (* this is in principle a little better than promote_scalar since it is constexpr *)