Fix: revert optimizations
This commit is contained in:
@@ -15,10 +15,3 @@ desktop = []
|
||||
rand = "0.8"
|
||||
parking_lot = "0.12"
|
||||
arc-swap = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
|
||||
[[bench]]
|
||||
name = "forth_vm"
|
||||
harness = false
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
use arc_swap::ArcSwap;
|
||||
use cagire_forth::{Dictionary, ExecutionTrace, Forth, Rng, StepContext, Variables};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use parking_lot::Mutex;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn new_vars() -> Variables {
|
||||
Arc::new(ArcSwap::from_pointee(HashMap::new()))
|
||||
}
|
||||
|
||||
fn new_dict() -> Dictionary {
|
||||
Arc::new(Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
fn new_rng() -> Rng {
|
||||
Arc::new(Mutex::new(StdRng::seed_from_u64(42)))
|
||||
}
|
||||
|
||||
fn new_forth() -> Forth {
|
||||
Forth::new(new_vars(), new_dict(), new_rng())
|
||||
}
|
||||
|
||||
fn ctx() -> StepContext<'static> {
|
||||
StepContext {
|
||||
step: 3,
|
||||
beat: 7.5,
|
||||
bank: 0,
|
||||
pattern: 1,
|
||||
tempo: 120.0,
|
||||
phase: 0.75,
|
||||
slot: 0,
|
||||
runs: 5,
|
||||
iter: 5,
|
||||
speed: 1.0,
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_access: None,
|
||||
speed_key: "__speed_0_1__",
|
||||
mouse_x: 0.5,
|
||||
mouse_y: 0.5,
|
||||
mouse_down: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
const SCRIPTS: &[(&str, &str)] = &[
|
||||
("trivial", "60"),
|
||||
("drum_hit", "\"kick\" s ."),
|
||||
("synth_params", "\"sine\" s 60 note 0.5 amp 2000 lpf ."),
|
||||
("cycle_notes", "{ 60 } { 64 } { 67 } 3 cycle note"),
|
||||
(
|
||||
"conditional",
|
||||
"step 2 mod 0 = if \"kick\" s . else \"hat\" s . then",
|
||||
),
|
||||
("variables", "beat 4 mod \"myvar\" !"),
|
||||
(
|
||||
"multi_emit",
|
||||
"\"kick\" s . \"hat\" s 0.7 amp . \"snare\" s 0.5 amp .",
|
||||
),
|
||||
(
|
||||
"complex",
|
||||
"{ 60 } { 64 } { 67 } { 72 } 4 cycle note \"sine\" s 0.01 0.1 0.7 0.3 adsr 2000 lpf .",
|
||||
),
|
||||
];
|
||||
|
||||
fn bench_evaluate(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("evaluate");
|
||||
let step_ctx = ctx();
|
||||
|
||||
for &(name, script) in SCRIPTS {
|
||||
group.bench_function(name, |b| {
|
||||
let mut forth = new_forth();
|
||||
b.iter(|| {
|
||||
let _ = black_box(forth.evaluate(black_box(script), &step_ctx));
|
||||
forth.clear_stack();
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_evaluate_with_trace(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("evaluate_with_trace");
|
||||
let step_ctx = ctx();
|
||||
|
||||
for &(name, script) in SCRIPTS {
|
||||
group.bench_function(name, |b| {
|
||||
let mut forth = new_forth();
|
||||
let mut trace = ExecutionTrace::default();
|
||||
b.iter(|| {
|
||||
trace.executed_spans.clear();
|
||||
trace.selected_spans.clear();
|
||||
trace.resolved.clear();
|
||||
let _ = black_box(forth.evaluate_with_trace(black_box(script), &step_ctx, &mut trace));
|
||||
forth.clear_stack();
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_trace_overhead(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("trace_overhead");
|
||||
let step_ctx = ctx();
|
||||
let script = "{ 60 } { 64 } { 67 } { 72 } 4 cycle note \"sine\" s 0.01 0.1 0.7 0.3 adsr 2000 lpf .";
|
||||
|
||||
group.bench_function("no_trace", |b| {
|
||||
let mut forth = new_forth();
|
||||
b.iter(|| {
|
||||
let _ = black_box(forth.evaluate(black_box(script), &step_ctx));
|
||||
forth.clear_stack();
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("with_trace", |b| {
|
||||
let mut forth = new_forth();
|
||||
let mut trace = ExecutionTrace::default();
|
||||
b.iter(|| {
|
||||
trace.executed_spans.clear();
|
||||
trace.selected_spans.clear();
|
||||
trace.resolved.clear();
|
||||
let _ = black_box(forth.evaluate_with_trace(black_box(script), &step_ctx, &mut trace));
|
||||
forth.clear_stack();
|
||||
});
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_tick_simulation(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("tick_simulation");
|
||||
|
||||
let patterns: &[(&str, &str)] = &[
|
||||
("drum_pattern", "\"kick\" s ."),
|
||||
("melodic_pattern", "{ 60 } { 64 } { 67 } { 72 } 4 cycle note \"sine\" s 0.5 amp ."),
|
||||
("generative_pattern", "1 8 rand note \"pluck\" s 0.3 0.8 rand amp ."),
|
||||
("conditional_pattern", "step 4 mod 0 = if \"kick\" s . else step 2 mod 0 = if \"hat\" s 0.5 amp . then then"),
|
||||
];
|
||||
|
||||
for &(name, script) in patterns {
|
||||
group.bench_function(name, |b| {
|
||||
let mut forth = new_forth();
|
||||
let step_ctx = ctx();
|
||||
let mut trace = ExecutionTrace::default();
|
||||
b.iter(|| {
|
||||
trace.executed_spans.clear();
|
||||
trace.selected_spans.clear();
|
||||
trace.resolved.clear();
|
||||
let _ = black_box(forth.evaluate_with_trace(black_box(script), &step_ctx, &mut trace));
|
||||
forth.clear_stack();
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("throughput");
|
||||
|
||||
let medium_scripts: &[&str] = &[
|
||||
"\"kick\" s .",
|
||||
"\"hat\" s 0.5 amp .",
|
||||
"\"snare\" s 0.8 amp 1000 lpf .",
|
||||
"{ 60 } { 64 } { 67 } 3 cycle note \"sine\" s .",
|
||||
"\"bass\" s 36 note 0.7 amp .",
|
||||
"{ 48 } { 55 } { 60 } { 64 } 4 cycle note \"pad\" s 0.3 amp 800 lpf .",
|
||||
"\"pluck\" s 72 note 0.4 amp 3000 lpf .",
|
||||
"step 4 mod 0 = if \"kick\" s . then",
|
||||
];
|
||||
|
||||
group.bench_function("8_patterns_one_step", |b| {
|
||||
let mut forths: Vec<Forth> = (0..8).map(|_| new_forth()).collect();
|
||||
let step_ctx = ctx();
|
||||
let mut trace = ExecutionTrace::default();
|
||||
b.iter(|| {
|
||||
for (i, forth) in forths.iter_mut().enumerate() {
|
||||
trace.executed_spans.clear();
|
||||
trace.selected_spans.clear();
|
||||
trace.resolved.clear();
|
||||
let _ = black_box(forth.evaluate_with_trace(
|
||||
black_box(medium_scripts[i]),
|
||||
&step_ctx,
|
||||
&mut trace,
|
||||
));
|
||||
forth.clear_stack();
|
||||
}
|
||||
});
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_evaluate,
|
||||
bench_evaluate_with_trace,
|
||||
bench_trace_overhead,
|
||||
bench_tick_simulation,
|
||||
bench_throughput,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
@@ -76,6 +76,7 @@ pub type VariablesMap = HashMap<String, Value>;
|
||||
pub type Variables = Arc<ArcSwap<VariablesMap>>;
|
||||
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
|
||||
pub type Rng = Arc<Mutex<StdRng>>;
|
||||
pub type Stack = Mutex<Vec<Value>>;
|
||||
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(&'static str, Value)]);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Stack-based Forth interpreter with audio command generation.
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::{Rng as RngTrait, SeedableRng};
|
||||
use std::borrow::Cow;
|
||||
@@ -9,45 +10,43 @@ use std::sync::Arc;
|
||||
use super::compiler::compile_script;
|
||||
use super::ops::Op;
|
||||
use super::types::{
|
||||
CmdRegister, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, StepContext, Value,
|
||||
Variables, VariablesMap,
|
||||
CmdRegister, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, Stack, StepContext,
|
||||
Value, Variables, VariablesMap,
|
||||
};
|
||||
|
||||
pub struct Forth {
|
||||
stack: Vec<Value>,
|
||||
stack: Stack,
|
||||
vars: Variables,
|
||||
dict: Dictionary,
|
||||
rng: Rng,
|
||||
cache: HashMap<String, Arc<[Op]>>,
|
||||
}
|
||||
|
||||
impl Forth {
|
||||
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
|
||||
Self {
|
||||
stack: Vec::new(),
|
||||
stack: Mutex::new(Vec::new()),
|
||||
vars,
|
||||
dict,
|
||||
rng,
|
||||
cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stack(&self) -> Vec<Value> {
|
||||
self.stack.clone()
|
||||
self.stack.lock().clone()
|
||||
}
|
||||
|
||||
pub fn clear_stack(&mut self) {
|
||||
self.stack.clear();
|
||||
pub fn clear_stack(&self) {
|
||||
self.stack.lock().clear();
|
||||
}
|
||||
|
||||
pub fn evaluate(&mut self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> {
|
||||
pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> {
|
||||
let (outputs, var_writes) = self.evaluate_impl(script, ctx, None)?;
|
||||
self.apply_var_writes(var_writes);
|
||||
Ok(outputs)
|
||||
}
|
||||
|
||||
pub fn evaluate_with_trace(
|
||||
&mut self,
|
||||
&self,
|
||||
script: &str,
|
||||
ctx: &StepContext,
|
||||
trace: &mut ExecutionTrace,
|
||||
@@ -58,7 +57,7 @@ impl Forth {
|
||||
}
|
||||
|
||||
pub fn evaluate_raw(
|
||||
&mut self,
|
||||
&self,
|
||||
script: &str,
|
||||
ctx: &StepContext,
|
||||
trace: &mut ExecutionTrace,
|
||||
@@ -78,7 +77,7 @@ impl Forth {
|
||||
}
|
||||
|
||||
fn evaluate_impl(
|
||||
&mut self,
|
||||
&self,
|
||||
script: &str,
|
||||
ctx: &StepContext,
|
||||
trace: Option<&mut ExecutionTrace>,
|
||||
@@ -87,35 +86,26 @@ impl Forth {
|
||||
return Err("empty script".into());
|
||||
}
|
||||
|
||||
let ops = if let Some(cached) = self.cache.get(script) {
|
||||
Arc::clone(cached)
|
||||
} else {
|
||||
let compiled = compile_script(script, &self.dict)?;
|
||||
let ops: Arc<[Op]> = Arc::from(compiled);
|
||||
self.cache.insert(script.to_owned(), Arc::clone(&ops));
|
||||
ops
|
||||
};
|
||||
let ops = compile_script(script, &self.dict)?;
|
||||
self.execute(&ops, ctx, trace)
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
&self,
|
||||
ops: &[Op],
|
||||
ctx: &StepContext,
|
||||
trace: Option<&mut ExecutionTrace>,
|
||||
) -> Result<(Vec<String>, HashMap<String, Value>), String> {
|
||||
let mut stack = self.stack.lock();
|
||||
let mut outputs: Vec<String> = Vec::with_capacity(8);
|
||||
let mut cmd = CmdRegister::new();
|
||||
let vars_snapshot = self.vars.load_full();
|
||||
let mut var_writes: Vec<(String, Value)> = Vec::new();
|
||||
let mut var_writes: HashMap<String, Value> = HashMap::new();
|
||||
|
||||
Self::execute_ops(
|
||||
&self.rng,
|
||||
&self.dict,
|
||||
&mut self.cache,
|
||||
self.execute_ops(
|
||||
ops,
|
||||
ctx,
|
||||
&mut self.stack,
|
||||
&mut stack,
|
||||
&mut outputs,
|
||||
&mut cmd,
|
||||
trace,
|
||||
@@ -123,15 +113,13 @@ impl Forth {
|
||||
&mut var_writes,
|
||||
)?;
|
||||
|
||||
let var_map: HashMap<String, Value> = var_writes.into_iter().collect();
|
||||
Ok((outputs, var_map))
|
||||
Ok((outputs, var_writes))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::only_used_in_recursion)]
|
||||
fn execute_ops(
|
||||
rng: &Rng,
|
||||
dict: &Dictionary,
|
||||
cache: &mut HashMap<String, Arc<[Op]>>,
|
||||
&self,
|
||||
ops: &[Op],
|
||||
ctx: &StepContext,
|
||||
stack: &mut Vec<Value>,
|
||||
@@ -139,14 +127,11 @@ impl Forth {
|
||||
cmd: &mut CmdRegister,
|
||||
trace: Option<&mut ExecutionTrace>,
|
||||
vars_snapshot: &VariablesMap,
|
||||
var_writes: &mut Vec<(String, Value)>,
|
||||
var_writes: &mut HashMap<String, Value>,
|
||||
) -> Result<(), String> {
|
||||
let mut pc = 0;
|
||||
let trace_cell = std::cell::RefCell::new(trace);
|
||||
let var_writes_cell = std::cell::RefCell::new(Some(var_writes));
|
||||
let rng_ref = rng;
|
||||
let dict_ref = dict;
|
||||
let cache_cell = std::cell::RefCell::new(Some(cache));
|
||||
|
||||
let run_quotation = |quot: Value,
|
||||
stack: &mut Vec<Value>,
|
||||
@@ -163,12 +148,7 @@ impl Forth {
|
||||
let mut trace_opt = trace_cell.borrow_mut().take();
|
||||
let mut var_writes_guard = var_writes_cell.borrow_mut();
|
||||
let vw = var_writes_guard.as_mut().expect("var_writes taken");
|
||||
let mut cache_guard = cache_cell.borrow_mut();
|
||||
let c = cache_guard.as_mut().expect("cache taken");
|
||||
Self::execute_ops(
|
||||
rng_ref,
|
||||
dict_ref,
|
||||
c,
|
||||
self.execute_ops(
|
||||
"_ops,
|
||||
ctx,
|
||||
stack,
|
||||
@@ -178,7 +158,6 @@ impl Forth {
|
||||
vars_snapshot,
|
||||
vw,
|
||||
)?;
|
||||
drop(cache_guard);
|
||||
drop(var_writes_guard);
|
||||
*trace_cell.borrow_mut() = trace_opt;
|
||||
Ok(())
|
||||
@@ -376,9 +355,9 @@ impl Forth {
|
||||
ensure(stack, count)?;
|
||||
let start = stack.len() - count;
|
||||
let slice = &mut stack[start..];
|
||||
let mut rng_guard = rng.lock();
|
||||
let mut rng = self.rng.lock();
|
||||
for i in (1..slice.len()).rev() {
|
||||
let j = rng_guard.gen_range(0..=i);
|
||||
let j = rng.gen_range(0..=i);
|
||||
slice.swap(i, j);
|
||||
}
|
||||
}
|
||||
@@ -641,10 +620,7 @@ impl Forth {
|
||||
let vw = var_writes_cell.borrow();
|
||||
let vw_ref = vw.as_ref().expect("var_writes taken");
|
||||
let val = vw_ref
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == name)
|
||||
.map(|(_, v)| v)
|
||||
.get(name)
|
||||
.or_else(|| vars_snapshot.get(name))
|
||||
.cloned()
|
||||
.unwrap_or(Value::Int(0, None));
|
||||
@@ -659,7 +635,7 @@ impl Forth {
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.expect("var_writes taken")
|
||||
.push((name, val));
|
||||
.insert(name, val);
|
||||
}
|
||||
Op::SetKeep => {
|
||||
let name = pop(stack)?;
|
||||
@@ -669,7 +645,7 @@ impl Forth {
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.expect("var_writes taken")
|
||||
.push((name, val));
|
||||
.insert(name, val);
|
||||
}
|
||||
|
||||
Op::GetContext(name) => {
|
||||
@@ -704,7 +680,7 @@ impl Forth {
|
||||
} else {
|
||||
(*b_i, *a_i)
|
||||
};
|
||||
let val = rng.lock().gen_range(lo..=hi);
|
||||
let val = self.rng.lock().gen_range(lo..=hi);
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Int(val));
|
||||
stack.push(Value::Int(val, None));
|
||||
}
|
||||
@@ -715,7 +691,7 @@ impl Forth {
|
||||
let val = if (hi - lo).abs() < f64::EPSILON {
|
||||
lo
|
||||
} else {
|
||||
rng.lock().gen_range(lo..hi)
|
||||
self.rng.lock().gen_range(lo..hi)
|
||||
};
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
|
||||
stack.push(Value::Float(val, None));
|
||||
@@ -729,7 +705,7 @@ impl Forth {
|
||||
return Err("exprand requires positive values".into());
|
||||
}
|
||||
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
||||
let u: f64 = rng.lock().gen();
|
||||
let u: f64 = self.rng.lock().gen();
|
||||
let val = lo * (hi / lo).powf(u);
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
|
||||
stack.push(Value::Float(val, None));
|
||||
@@ -741,14 +717,14 @@ impl Forth {
|
||||
return Err("logrand requires positive values".into());
|
||||
}
|
||||
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
||||
let u: f64 = rng.lock().gen();
|
||||
let u: f64 = self.rng.lock().gen();
|
||||
let val = hi * (lo / hi).powf(u);
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
Op::Seed => {
|
||||
let s = pop_int(stack)?;
|
||||
*rng.lock() = StdRng::seed_from_u64(s as u64);
|
||||
*self.rng.lock() = StdRng::seed_from_u64(s as u64);
|
||||
}
|
||||
|
||||
Op::Cycle(word_span) | Op::PCycle(word_span) => {
|
||||
@@ -775,7 +751,7 @@ impl Forth {
|
||||
if count == 0 {
|
||||
return Err("choose count must be > 0".into());
|
||||
}
|
||||
let idx = rng.lock().gen_range(0..count);
|
||||
let idx = self.rng.lock().gen_range(0..count);
|
||||
if let Some(span) = word_span {
|
||||
if stack.len() >= count {
|
||||
let start = stack.len() - count;
|
||||
@@ -832,7 +808,7 @@ impl Forth {
|
||||
if total <= 0.0 {
|
||||
return Err("wchoose: total weight must be > 0".into());
|
||||
}
|
||||
let threshold: f64 = rng.lock().gen::<f64>() * total;
|
||||
let threshold: f64 = self.rng.lock().gen::<f64>() * total;
|
||||
let mut cumulative = 0.0;
|
||||
let mut selected_idx = count - 1;
|
||||
for (i, &w) in weights.iter().enumerate() {
|
||||
@@ -850,7 +826,7 @@ impl Forth {
|
||||
Op::ChanceExec(word_span) | Op::ProbExec(word_span) => {
|
||||
let threshold = pop_float(stack)?;
|
||||
let quot = pop(stack)?;
|
||||
let val: f64 = rng.lock().gen();
|
||||
let val: f64 = self.rng.lock().gen();
|
||||
let limit = match &ops[pc] {
|
||||
Op::ChanceExec(_) => threshold,
|
||||
_ => threshold / 100.0,
|
||||
@@ -863,7 +839,7 @@ impl Forth {
|
||||
}
|
||||
|
||||
Op::Coin(word_span) => {
|
||||
let val: f64 = rng.lock().gen();
|
||||
let val: f64 = self.rng.lock().gen();
|
||||
let result = val < 0.5;
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
|
||||
stack.push(Value::Int(if result { 1 } else { 0 }, None));
|
||||
@@ -1003,7 +979,7 @@ impl Forth {
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.expect("var_writes taken")
|
||||
.push(("__tempo__".to_string(), Value::Float(clamped, None)));
|
||||
.insert("__tempo__".to_string(), Value::Float(clamped, None));
|
||||
}
|
||||
|
||||
Op::SetSpeed => {
|
||||
@@ -1013,7 +989,7 @@ impl Forth {
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.expect("var_writes taken")
|
||||
.push((ctx.speed_key.to_string(), Value::Float(clamped, None)));
|
||||
.insert(ctx.speed_key.to_string(), Value::Float(clamped, None));
|
||||
}
|
||||
|
||||
Op::Loop => {
|
||||
@@ -1175,7 +1151,7 @@ impl Forth {
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.expect("var_writes taken")
|
||||
.push(("i".to_string(), Value::Int(i, None)));
|
||||
.insert("i".to_string(), Value::Int(i, None));
|
||||
run_quotation(quot.clone(), stack, outputs, cmd)?;
|
||||
}
|
||||
}
|
||||
@@ -1411,12 +1387,7 @@ impl Forth {
|
||||
}
|
||||
Op::Forget => {
|
||||
let name = pop(stack)?;
|
||||
dict.lock().remove(name.as_str()?);
|
||||
cache_cell
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.expect("cache taken")
|
||||
.clear();
|
||||
self.dict.lock().remove(name.as_str()?);
|
||||
}
|
||||
}
|
||||
pc += 1;
|
||||
|
||||
Reference in New Issue
Block a user