1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Issue #363 — an `:or` destructuring default is evaluated *eagerly*, in both
//! tiers.
//!
//! `destructure` expands a symbol carrying an `:or` entry to
//! `(get m :k default)`. `get` is an ordinary function, so its third argument
//! is evaluated whether or not the key is present: on the JVM a side-effecting
//! default fires on every call, even one that supplies the key.
//!
//! The tree-walker does that (`interp/destructure.rs`); the IR lowerer used to
//! emit the default into the `then` arm of a nil-check branch, so it fired only
//! on a miss. Since a function tiers up partway through a run — at a point set
//! by a background worker — that made the *number* of times a default fired
//! nondeterministic. These tests run the same programs through both tiers and
//! demand the same answer, so the two cannot drift apart again.
//!
//! This file flips the process-wide eager-lowering switch, so it lives in its
//! own test binary; the tree-walk cases below are unaffected by it, since
//! `ExecutionMode::TreeWalk` never consults the IR path at all.
use std::sync::Arc;
use cljrs_reader::Parser;
use cljrs_runtime::env::env::{Env, GlobalEnv};
use cljrs_value::Value;
fn make_env(mode: cljrs_runtime::ExecutionMode) -> (Arc<GlobalEnv>, Env) {
// Process-wide, and the reason this test is its own binary.
cljrs_runtime::tiered::force_eager_lowering();
let globals = cljrs_runtime::Runtime::builder()
.execution_mode(mode)
.build()
.expect("runtime")
.into_globals();
let env = Env::new(globals.clone(), "user");
(globals, env)
}
fn eval_pr(mode: cljrs_runtime::ExecutionMode, src: &str) -> String {
let (_globals, mut env) = make_env(mode);
let mut parser = Parser::new(src.to_string(), "<test>".to_string());
let forms = parser.parse_all().expect("parse error");
let mut result = Value::Nil;
for form in forms {
result = cljrs_runtime::interp::eval::eval(&form, &mut env).expect("eval error");
}
match result {
Value::Str(s) => s.get().as_str().to_string(),
// The type name, not `{:?}`: which type came back instead of a string
// is what the assertion is about, and a `Value` may hold a secret.
other => panic!("expected a string from pr-str, got a {}", other.type_name()),
}
}
/// Run `src` through the tree-walker and through the IR tier and assert both
/// produce `expected` — the tier split in #363 is exactly a disagreement here.
fn assert_both_tiers(src: &str, expected: &str) {
assert_eq!(
eval_pr(cljrs_runtime::ExecutionMode::TreeWalk, src),
expected,
"tree-walking interpreter"
);
assert_eq!(
eval_pr(cljrs_runtime::ExecutionMode::TieredNoJit, src),
expected,
"IR tier"
);
}
#[test]
fn a_present_key_still_evaluates_its_default() {
// The discriminating case: the key is supplied on every call, so a lazy
// lowering never runs the default and reports 0.
assert_both_tiers(
"(def counter (atom 0))
(defn f [{:keys [x] :or {x (do (swap! counter inc) 1)}}] x)
(dotimes [_ 200] (f {:x 99}))
(pr-str @counter)",
"200",
);
}
#[test]
fn a_present_key_still_wins_over_its_default() {
// Eager evaluation must not change which value is *bound*.
assert_both_tiers(
"(def counter (atom 0))
(defn f [{:keys [x] :or {x (do (swap! counter inc) 1)}}] x)
(pr-str (mapv f (repeat 200 {:x 99})))",
&format!("[{}]", vec!["99"; 200].join(" ")),
);
}
#[test]
fn a_missing_key_binds_the_default() {
assert_both_tiers(
"(defn f [{:keys [x] :or {x 7}}] x)
(pr-str (mapv f (repeat 200 {})))",
&format!("[{}]", vec!["7"; 200].join(" ")),
);
}
#[test]
fn a_default_evaluates_once_per_call_not_once_per_lowering() {
// A miss and a hit interleaved: 200 calls, 200 evaluations either way.
assert_both_tiers(
"(def counter (atom 0))
(defn f [{:keys [x] :or {x (do (swap! counter inc) 1)}}] x)
(dotimes [i 200] (f (if (even? i) {:x 99} {})))
(pr-str @counter)",
"200",
);
}
#[test]
fn strs_and_syms_defaults_are_eager_too() {
// `:strs` and `:syms` share `lower_with_default`; pin them alongside.
assert_both_tiers(
"(def counter (atom 0))
(defn f [{:strs [x] :or {x (do (swap! counter inc) 1)}}] x)
(dotimes [_ 200] (f {\"x\" 99}))
(pr-str @counter)",
"200",
);
assert_both_tiers(
"(def counter (atom 0))
(defn f [{:syms [x] :or {x (do (swap! counter inc) 1)}}] x)
(dotimes [_ 200] (f {'x 99}))
(pr-str @counter)",
"200",
);
}
#[test]
fn an_explicit_binding_pairs_default_is_eager_too() {
// `{a :x}` rather than `:keys` — the other `apply_default_if_nil` site.
assert_both_tiers(
"(def counter (atom 0))
(defn f [{a :x :or {a (do (swap! counter inc) 1)}}] a)
(dotimes [_ 200] (f {:x 99}))
(pr-str @counter)",
"200",
);
}
#[test]
fn a_let_destructuring_default_is_eager_too() {
// The same lowering serves `let*`, reached here through a hot function.
assert_both_tiers(
"(def counter (atom 0))
(defn f [m] (let [{:keys [x] :or {x (do (swap! counter inc) 1)}} m] x))
(dotimes [_ 200] (f {:x 99}))
(pr-str @counter)",
"200",
);
}