hexodsp/wblockdsp/
compiler.rs

1// Copyright (c) 2022 Weird Constructor <weirdconstructor@gmail.com>
2// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
3// See README.md and COPYING for details.
4
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::rc::Rc;
8
9use super::language::*;
10#[cfg(feature = "synfx-dsp-jit")]
11use synfx_dsp_jit::{ASTNode, JITCompileError};
12
13#[derive(Debug)]
14struct JASTNode {
15    id: usize,
16    typ: String,
17    lbl: String,
18    nodes: Vec<(String, String, ASTNodeRef)>,
19}
20
21/// The WBlockDSP Abstract Syntax Tree. It is generated by [BlockFun::generate_tree]
22/// in [Block2JITCompiler::compile].
23#[derive(Debug, Clone)]
24pub struct ASTNodeRef(Rc<RefCell<JASTNode>>);
25
26impl BlockASTNode for ASTNodeRef {
27    fn from(id: usize, typ: &str, lbl: &str) -> ASTNodeRef {
28        ASTNodeRef(Rc::new(RefCell::new(JASTNode {
29            id,
30            typ: typ.to_string(),
31            lbl: lbl.to_string(),
32            nodes: vec![],
33        })))
34    }
35
36    fn add_node(&self, in_port: String, out_port: String, node: ASTNodeRef) {
37        self.0.borrow_mut().nodes.push((in_port, out_port, node));
38    }
39}
40
41impl ASTNodeRef {
42    /// Returns the first child AST node.
43    pub fn first_child_ref(&self) -> Option<ASTNodeRef> {
44        self.0.borrow().nodes.get(0).map(|n| n.2.clone())
45    }
46
47    /// Returns the first child, including input/output info.
48    pub fn first_child(&self) -> Option<(String, String, ASTNodeRef)> {
49        self.0.borrow().nodes.get(0).cloned()
50    }
51
52    /// Returns the nth child, including input/output info.
53    pub fn nth_child(&self, i: usize) -> Option<(String, String, ASTNodeRef)> {
54        self.0.borrow().nodes.get(i).cloned()
55    }
56
57    /// Generates a recursive tree dump output.
58    ///
59    ///```ignore
60    /// println!("{}", node.walk_dump("", "", 0));
61    ///```
62    pub fn walk_dump(&self, input: &str, output: &str, indent: usize) -> String {
63        let indent_str = "   ".repeat(indent + 1);
64
65        let out_port = if output.len() > 0 { format!("(out: {})", output) } else { "".to_string() };
66        let in_port = if input.len() > 0 { format!("(in: {})", input) } else { "".to_string() };
67
68        let mut s = format!(
69            "{}{}#{}[{}] {}{}\n",
70            indent_str,
71            self.0.borrow().id,
72            self.0.borrow().typ,
73            self.0.borrow().lbl,
74            out_port,
75            in_port
76        );
77
78        for (inp, out, n) in &self.0.borrow().nodes {
79            s += &n.walk_dump(&inp, &out, indent + 1);
80        }
81
82        s
83    }
84}
85
86type BlkASTRef = Rc<BlkASTNode>;
87
88#[derive(Debug, Clone)]
89enum BlkASTNode {
90    Area {
91        childs: Vec<BlkASTRef>,
92    },
93    Set {
94        var: String,
95        expr: BlkASTRef,
96    },
97    Get {
98        id: usize,
99        var: String,
100    },
101    Node {
102        id: usize,
103        out: Option<String>,
104        typ: String,
105        lbl: String,
106        childs: Vec<(Option<String>, BlkASTRef)>,
107    },
108    Literal {
109        value: f64,
110    },
111}
112
113impl BlkASTNode {
114    pub fn dump(&self, indent: usize, inp: Option<&str>) -> String {
115        let mut indent_str = "   ".repeat(indent + 1);
116
117        if let Some(inp) = inp {
118            indent_str += &format!("{}<= ", inp);
119        } else {
120            indent_str += "<= ";
121        }
122
123        match self {
124            BlkASTNode::Area { childs } => {
125                let mut s = format!("{}Area\n", indent_str);
126                for c in childs.iter() {
127                    s += &c.dump(indent + 1, None);
128                }
129                s
130            }
131            BlkASTNode::Set { var, expr } => {
132                format!("{}set '{}'=\n", indent_str, var) + &expr.dump(indent + 1, None)
133            }
134            BlkASTNode::Get { id, var } => {
135                format!("{}get '{}' (id={})\n", indent_str, var, id)
136            }
137            BlkASTNode::Literal { value } => {
138                format!("{}{}\n", indent_str, value)
139            }
140            BlkASTNode::Node { id, out, typ, lbl, childs } => {
141                let lbl = if *typ == *lbl { "".to_string() } else { format!("[{}]", lbl) };
142
143                let mut s = if let Some(out) = out {
144                    format!("{}{}{} (id={}/{})\n", indent_str, typ, lbl, id, out)
145                } else {
146                    format!("{}{}{} (id={})\n", indent_str, typ, lbl, id)
147                };
148                for (inp, c) in childs.iter() {
149                    s += &format!("{}", c.dump(indent + 1, inp.as_ref().map(|s| &s[..])));
150                }
151                s
152            }
153        }
154    }
155
156    pub fn new_area(childs: Vec<BlkASTRef>) -> BlkASTRef {
157        Rc::new(BlkASTNode::Area { childs })
158    }
159
160    pub fn new_set(var: &str, expr: BlkASTRef) -> BlkASTRef {
161        Rc::new(BlkASTNode::Set { var: var.to_string(), expr })
162    }
163
164    pub fn new_get(id: usize, var: &str) -> BlkASTRef {
165        Rc::new(BlkASTNode::Get { id, var: var.to_string() })
166    }
167
168    pub fn new_literal(val: &str) -> Result<BlkASTRef, BlkJITCompileError> {
169        if let Ok(value) = val.parse::<f64>() {
170            Ok(Rc::new(BlkASTNode::Literal { value }))
171        } else {
172            Err(BlkJITCompileError::BadLiteralNumber(val.to_string()))
173        }
174    }
175
176    pub fn new_node(
177        id: usize,
178        out: Option<String>,
179        typ: &str,
180        lbl: &str,
181        childs: Vec<(Option<String>, BlkASTRef)>,
182    ) -> BlkASTRef {
183        Rc::new(BlkASTNode::Node { id, out, typ: typ.to_string(), lbl: lbl.to_string(), childs })
184    }
185}
186
187#[derive(Debug, Clone)]
188pub enum BlkJITCompileError {
189    UnknownError,
190    NoSynfxDSPJit,
191    BadTree(ASTNodeRef),
192    NoOutputAtIdx(String, usize),
193    ASTMissingOutputLabel(usize),
194    NoTmpVarForOutput(usize, String),
195    BadLiteralNumber(String),
196    NodeWithoutID(String),
197    UnknownType(String),
198    TooManyInputs(String, usize),
199    WrongNumberOfChilds(String, usize, usize),
200    UnassignedInput(String, usize, String),
201    #[cfg(feature = "synfx-dsp-jit")]
202    JITCompileError(JITCompileError),
203}
204
205pub struct Block2JITCompiler {
206    idout_var_map: HashMap<String, String>,
207    lang: Rc<RefCell<BlockLanguage>>,
208    tmpvar_counter: usize,
209}
210
211#[cfg(not(feature = "synfx-dsp-jit"))]
212pub enum ASTNode {
213    NoSynfxDSPJit,
214}
215
216impl Block2JITCompiler {
217    pub fn new(lang: Rc<RefCell<BlockLanguage>>) -> Self {
218        Self { idout_var_map: HashMap::new(), lang, tmpvar_counter: 0 }
219    }
220
221    pub fn next_tmpvar_name(&mut self, extra: &str) -> String {
222        self.tmpvar_counter += 1;
223        format!("_tmp{}_{}_", self.tmpvar_counter, extra)
224    }
225
226    pub fn store_idout_var(&mut self, id: usize, out: &str, v: &str) {
227        self.idout_var_map.insert(format!("{}/{}", id, out), v.to_string());
228    }
229
230    pub fn get_var_for_idout(&self, id: usize, out: &str) -> Option<&str> {
231        self.idout_var_map.get(&format!("{}/{}", id, out)).map(|s| &s[..])
232    }
233
234    fn trans2bjit(
235        &mut self,
236        node: &ASTNodeRef,
237        my_out: Option<String>,
238    ) -> Result<BlkASTRef, BlkJITCompileError> {
239        let id = node.0.borrow().id;
240
241        if let Some(out) = &my_out {
242            if let Some(tmpvar) = self.get_var_for_idout(id, out) {
243                return Ok(BlkASTNode::new_get(0, tmpvar));
244            }
245        } else if let Some(tmpvar) = self.get_var_for_idout(id, "") {
246            return Ok(BlkASTNode::new_get(0, tmpvar));
247        }
248
249        match &node.0.borrow().typ[..] {
250            "<a>" => {
251                let mut childs = vec![];
252
253                let mut i = 0;
254                while let Some((_in, out, child)) = node.nth_child(i) {
255                    let out = if out.len() > 0 { Some(out) } else { None };
256                    let child = self.trans2bjit(&child, out)?;
257                    childs.push(child);
258                    i += 1;
259                }
260
261                Ok(BlkASTNode::new_area(childs))
262            }
263            // TODO: handle results properly, like remembering the most recent result
264            // and append it to the end of the statements block. so that a temporary
265            // variable is created.
266            "<r>" => {
267                if let Some((_in, out, first)) = node.first_child() {
268                    let out = if out.len() > 0 { Some(out) } else { None };
269                    let childs =
270                        vec![self.trans2bjit(&first, out)?, BlkASTNode::new_get(0, "_res_")];
271                    Ok(BlkASTNode::new_area(childs))
272                } else {
273                    Err(BlkJITCompileError::BadTree(node.clone()))
274                }
275            }
276            "->" => {
277                if let Some((_in, out, first)) = node.first_child() {
278                    let out = if out.len() > 0 { Some(out) } else { None };
279                    self.trans2bjit(&first, out)
280                } else {
281                    Err(BlkJITCompileError::BadTree(node.clone()))
282                }
283            }
284            "value" => Ok(BlkASTNode::new_literal(&node.0.borrow().lbl)?),
285            "set" | "<res>" => {
286                if let Some((_in, out, first)) = node.first_child() {
287                    let out = if out.len() > 0 { Some(out) } else { None };
288                    let expr = self.trans2bjit(&first, out)?;
289                    if &node.0.borrow().typ[..] == "<res>" {
290                        Ok(BlkASTNode::new_set("_res_", expr))
291                    } else {
292                        Ok(BlkASTNode::new_set(&node.0.borrow().lbl, expr))
293                    }
294                } else {
295                    Err(BlkJITCompileError::BadTree(node.clone()))
296                }
297            }
298            "get" => Ok(BlkASTNode::new_get(id, &node.0.borrow().lbl)),
299            "->2" | "->3" => {
300                if let Some((_in, out, first)) = node.first_child() {
301                    let out = if out.len() > 0 { Some(out) } else { None };
302                    let mut area = vec![];
303                    let tmp_var = self.next_tmpvar_name("");
304                    let expr = self.trans2bjit(&first, out)?;
305                    area.push(BlkASTNode::new_set(&tmp_var, expr));
306                    area.push(BlkASTNode::new_get(0, &tmp_var));
307                    self.store_idout_var(id, "", &tmp_var);
308                    Ok(BlkASTNode::new_area(area))
309                } else {
310                    Err(BlkJITCompileError::BadTree(node.clone()))
311                }
312            }
313            optype => {
314                let mut childs = vec![];
315
316                let mut i = 0;
317                while let Some((inp, out, child)) = node.nth_child(i) {
318                    let out = if out.len() > 0 { Some(out) } else { None };
319
320                    let child = self.trans2bjit(&child, out)?;
321                    if inp.len() > 0 {
322                        childs.push((Some(inp.to_string()), child));
323                    } else {
324                        childs.push((None, child));
325                    }
326                    i += 1;
327                }
328
329                // TODO: Reorder the childs/arguments according to the input
330                //       order in the BlockLanguage
331
332                let cnt = self.lang.borrow().type_output_count(optype);
333                if cnt > 1 {
334                    let mut area = vec![];
335
336                    let oname = self.lang.borrow().get_output_name_at_index(optype, 0);
337
338                    if let Some(oname) = oname {
339                        let tmp_var = self.next_tmpvar_name(&oname);
340
341                        area.push(BlkASTNode::new_set(
342                            &tmp_var,
343                            BlkASTNode::new_node(
344                                id,
345                                my_out.clone(),
346                                &node.0.borrow().typ,
347                                &node.0.borrow().lbl,
348                                childs,
349                            ),
350                        ));
351                        self.store_idout_var(id, &oname, &tmp_var);
352                    } else {
353                        return Err(BlkJITCompileError::NoOutputAtIdx(optype.to_string(), 0));
354                    }
355
356                    for i in 1..cnt {
357                        let oname = self.lang.borrow().get_output_name_at_index(optype, i);
358
359                        if let Some(oname) = oname {
360                            let tmp_var = self.next_tmpvar_name(&oname);
361
362                            area.push(BlkASTNode::new_set(
363                                &tmp_var,
364                                BlkASTNode::new_get(0, &format!("%{}", i)),
365                            ));
366
367                            self.store_idout_var(id, &oname, &tmp_var);
368                        } else {
369                            return Err(BlkJITCompileError::NoOutputAtIdx(optype.to_string(), i));
370                        }
371                    }
372
373                    if let Some(out) = &my_out {
374                        if let Some(tmpvar) = self.get_var_for_idout(id, out) {
375                            area.push(BlkASTNode::new_get(0, tmpvar));
376                        } else {
377                            return Err(BlkJITCompileError::NoTmpVarForOutput(id, out.to_string()));
378                        }
379                    } else {
380                        return Err(BlkJITCompileError::ASTMissingOutputLabel(id));
381                    }
382
383                    Ok(BlkASTNode::new_area(area))
384                } else {
385                    Ok(BlkASTNode::new_node(
386                        id,
387                        my_out,
388                        &node.0.borrow().typ,
389                        &node.0.borrow().lbl,
390                        childs,
391                    ))
392                }
393            }
394        }
395    }
396
397    #[cfg(feature = "synfx-dsp-jit")]
398    fn bjit2jit(&mut self, ast: &BlkASTRef) -> Result<Box<ASTNode>, BlkJITCompileError> {
399        use synfx_dsp_jit::build::*;
400
401        match &**ast {
402            BlkASTNode::Area { childs } => {
403                let mut stmt = vec![];
404                for c in childs.iter() {
405                    stmt.push(self.bjit2jit(&c)?);
406                }
407                Ok(stmts(&stmt[..]))
408            }
409            BlkASTNode::Set { var, expr } => {
410                let e = self.bjit2jit(&expr)?;
411                Ok(assign(var, e))
412            }
413            BlkASTNode::Get { var: varname, .. } => Ok(var(varname)),
414            BlkASTNode::Node { id, typ, childs, .. } => match &typ[..] {
415                "if" => Err(BlkJITCompileError::UnknownError),
416                "zero" => Ok(literal(0.0)),
417                _ => {
418                    if *id == 0 {
419                        return Err(BlkJITCompileError::NodeWithoutID(typ.to_string()));
420                    }
421
422                    let lang = self.lang.clone();
423
424                    let mut args = vec![];
425
426                    if let Some(inputs) = lang.borrow().get_type_inputs(typ) {
427                        if childs.len() > inputs.len() {
428                            return Err(BlkJITCompileError::TooManyInputs(typ.to_string(), *id));
429                        }
430
431                        if inputs.len() > 0 && inputs[0] == Some("".to_string()) {
432                            if inputs.len() != childs.len() {
433                                return Err(BlkJITCompileError::WrongNumberOfChilds(
434                                    typ.to_string(),
435                                    *id,
436                                    childs.len(),
437                                ));
438                            }
439
440                            // We assume all inputs are unnamed:
441                            for (_inp, c) in childs.iter() {
442                                args.push(self.bjit2jit(&c)?);
443                            }
444                        } else {
445                            // We assume all inputs are named:
446                            for input_name in inputs.iter() {
447                                let mut found = false;
448                                for (inp, c) in childs.iter() {
449                                    println!("FOFOFO '{:?}' = '{:?}'", inp, input_name);
450                                    if inp == input_name {
451                                        args.push(self.bjit2jit(&c)?);
452                                        found = true;
453                                        break;
454                                    }
455                                }
456
457                                if !found {
458                                    return Err(BlkJITCompileError::UnassignedInput(
459                                        typ.to_string(),
460                                        *id,
461                                        format!("{:?}", input_name),
462                                    ));
463                                }
464                            }
465                        }
466                    } else {
467                        return Err(BlkJITCompileError::UnknownType(typ.to_string()));
468                    }
469
470                    match &typ[..] {
471                        "+" | "*" | "-" | "/" => {
472                            if args.len() != 2 {
473                                return Err(BlkJITCompileError::WrongNumberOfChilds(
474                                    typ.to_string(),
475                                    *id,
476                                    args.len(),
477                                ));
478                            }
479
480                            let a = args.remove(0);
481                            let b = args.remove(0);
482
483                            match &typ[..] {
484                                "+" => Ok(op_add(a, b)),
485                                "*" => Ok(op_mul(a, b)),
486                                "-" => Ok(op_sub(a, b)),
487                                "/" => Ok(op_div(a, b)),
488                                _ => Err(BlkJITCompileError::UnknownType(typ.to_string())),
489                            }
490                        }
491                        _ => Ok(call(typ, *id as u64, &args[..])),
492                    }
493                }
494            },
495            BlkASTNode::Literal { value } => Ok(literal(*value)),
496        }
497    }
498
499    pub fn compile(&mut self, fun: &BlockFun) -> Result<Box<ASTNode>, BlkJITCompileError> {
500        #[cfg(feature = "synfx-dsp-jit")]
501        {
502            let tree = fun.generate_tree::<ASTNodeRef>("zero").unwrap();
503            println!("{}", tree.walk_dump("", "", 0));
504
505            let blkast = self.trans2bjit(&tree, None)?;
506            println!("R: {}", blkast.dump(0, None));
507
508            self.bjit2jit(&blkast)
509        }
510        #[cfg(not(feature = "synfx-dsp-jit"))]
511        {
512            Err(BlkJITCompileError::NoSynfxDSPJit)
513        }
514    }
515}
516
517#[cfg(feature = "synfx-dsp-jit")]
518#[cfg(test)]
519mod test {
520    use super::*;
521
522    macro_rules! assert_float_eq {
523        ($a:expr, $b:expr) => {
524            if ($a - $b).abs() > 0.0001 {
525                panic!(
526                    r#"assertion failed: `(left == right)`
527      left: `{:?}`,
528     right: `{:?}`"#,
529                    $a, $b
530                )
531            }
532        };
533    }
534
535    fn put_n(bf: &mut BlockFun, a: usize, x: i64, y: i64, s: &str) {
536        bf.instanciate_at(a, x, y, s, None).expect("no put error");
537    }
538
539    fn put_v(bf: &mut BlockFun, a: usize, x: i64, y: i64, s: &str, v: &str) {
540        bf.instanciate_at(a, x, y, s, Some(v.to_string())).expect("no put error");
541    }
542
543    use synfx_dsp_jit::{get_standard_library, ASTFun, DSPFunction, DSPNodeContext, JIT};
544
545    fn new_jit_fun<F: FnMut(&mut BlockFun)>(
546        mut f: F,
547    ) -> (Rc<RefCell<DSPNodeContext>>, Box<DSPFunction>) {
548        let lib = get_standard_library();
549        let lang = crate::wblockdsp::setup_hxdsp_block_language(lib.clone());
550        let mut bf = BlockFun::new(lang.clone());
551
552        f(&mut bf);
553
554        let mut compiler = Block2JITCompiler::new(bf.block_language());
555        let ast = compiler.compile(&bf).expect("blk2jit compiles");
556        let ctx = DSPNodeContext::new_ref();
557        let jit = JIT::new(lib, ctx.clone());
558        let mut fun = jit.compile(ASTFun::new(ast)).expect("jit compiles");
559
560        fun.init(44100.0, None);
561
562        (ctx, fun)
563    }
564
565    #[test]
566    fn check_blocklang_sig1() {
567        let (ctx, mut fun) = new_jit_fun(|bf| {
568            put_v(bf, 0, 0, 1, "value", "0.3");
569            put_v(bf, 0, 1, 1, "set", "&sig1");
570            put_v(bf, 0, 0, 2, "value", "-0.3");
571            put_v(bf, 0, 1, 2, "set", "&sig2");
572            put_v(bf, 0, 0, 3, "value", "-1.3");
573        });
574
575        let (s1, s2, ret) = fun.exec_2in_2out(0.0, 0.0);
576
577        assert_float_eq!(s1, 0.3);
578        assert_float_eq!(s2, -0.3);
579        assert_float_eq!(ret, -1.3);
580
581        ctx.borrow_mut().free();
582    }
583
584    #[test]
585    fn check_blocklang_accum_shift() {
586        let (ctx, mut fun) = new_jit_fun(|bf| {
587            put_n(bf, 0, 1, 1, "accum");
588            bf.shift_port(0, 1, 1, 1, false);
589            put_v(bf, 0, 0, 2, "value", "0.01");
590            put_v(bf, 0, 0, 1, "get", "*reset");
591        });
592
593        fun.exec_2in_2out(0.0, 0.0);
594        fun.exec_2in_2out(0.0, 0.0);
595        fun.exec_2in_2out(0.0, 0.0);
596        let (_s1, _s2, ret) = fun.exec_2in_2out(0.0, 0.0);
597        assert_float_eq!(ret, 0.04);
598
599        let reset_idx = ctx.borrow().get_persistent_variable_index_by_name("*reset").unwrap();
600        fun.access_persistent_var(reset_idx).map(|reset| *reset = 1.0);
601
602        let (_s1, _s2, ret) = fun.exec_2in_2out(0.0, 0.0);
603        assert_float_eq!(ret, 0.0);
604
605        fun.access_persistent_var(reset_idx).map(|reset| *reset = 0.0);
606
607        fun.exec_2in_2out(0.0, 0.0);
608        fun.exec_2in_2out(0.0, 0.0);
609        fun.exec_2in_2out(0.0, 0.0);
610        fun.exec_2in_2out(0.0, 0.0);
611        let (_s1, _s2, ret) = fun.exec_2in_2out(0.0, 0.0);
612        assert_float_eq!(ret, 0.05);
613
614        ctx.borrow_mut().free();
615    }
616
617    #[test]
618    fn check_blocklang_arithmetics() {
619        // Check + and *
620        let (ctx, mut fun) = new_jit_fun(|bf| {
621            put_v(bf, 0, 0, 1, "value", "0.50");
622            put_v(bf, 0, 0, 2, "value", "0.01");
623            put_n(bf, 0, 1, 1, "+");
624            bf.shift_port(0, 1, 1, 1, true);
625            put_v(bf, 0, 1, 3, "value", "2.0");
626            put_n(bf, 0, 2, 2, "*");
627        });
628
629        let (_s1, _s2, ret) = fun.exec_2in_2out(0.0, 0.0);
630        assert_float_eq!(ret, 1.02);
631        ctx.borrow_mut().free();
632
633        // Check - and /
634        let (ctx, mut fun) = new_jit_fun(|bf| {
635            put_v(bf, 0, 0, 1, "value", "0.50");
636            put_v(bf, 0, 0, 2, "value", "0.01");
637            put_n(bf, 0, 1, 1, "-");
638            bf.shift_port(0, 1, 1, 1, true);
639            put_v(bf, 0, 1, 3, "value", "2.0");
640            put_n(bf, 0, 2, 2, "/");
641        });
642
643        let (_s1, _s2, ret) = fun.exec_2in_2out(0.0, 0.0);
644        assert_float_eq!(ret, (0.5 - 0.01) / 2.0);
645        ctx.borrow_mut().free();
646
647        // Check swapping inputs of "-"
648        let (ctx, mut fun) = new_jit_fun(|bf| {
649            put_v(bf, 0, 0, 1, "value", "0.50");
650            put_v(bf, 0, 0, 2, "value", "0.01");
651            put_n(bf, 0, 1, 1, "-");
652            bf.shift_port(0, 1, 1, 1, false);
653        });
654
655        let (_s1, _s2, ret) = fun.exec_2in_2out(0.0, 0.0);
656        assert_float_eq!(ret, 0.01 - 0.5);
657        ctx.borrow_mut().free();
658
659        // Check swapping inputs of "/"
660        let (ctx, mut fun) = new_jit_fun(|bf| {
661            put_v(bf, 0, 0, 1, "value", "0.50");
662            put_v(bf, 0, 0, 2, "value", "0.01");
663            put_n(bf, 0, 1, 1, "/");
664            bf.shift_port(0, 1, 1, 1, false);
665        });
666
667        let (_s1, _s2, ret) = fun.exec_2in_2out(0.0, 0.0);
668        assert_float_eq!(ret, 0.01 / 0.5);
669        ctx.borrow_mut().free();
670
671        // Check division of 0.0
672        let (ctx, mut fun) = new_jit_fun(|bf| {
673            put_v(bf, 0, 0, 1, "value", "0.50");
674            put_v(bf, 0, 0, 2, "value", "0.0");
675            put_n(bf, 0, 1, 1, "/");
676        });
677
678        let (_s1, _s2, ret) = fun.exec_2in_2out(0.0, 0.0);
679        assert_float_eq!(ret, 0.5 / 0.0);
680        ctx.borrow_mut().free();
681    }
682
683    #[test]
684    fn check_blocklang_divrem() {
685        // &sig1 on second output:
686        let (ctx, mut fun) = new_jit_fun(|bf| {
687            put_v(bf, 0, 0, 1, "value", "0.3");
688            put_v(bf, 0, 0, 2, "value", "-0.4");
689            put_n(bf, 0, 1, 1, "/%");
690            put_v(bf, 0, 2, 2, "set", "&sig1");
691        });
692
693        let (s1, _, ret) = fun.exec_2in_2out(0.0, 0.0);
694
695        assert_float_eq!(s1, 0.3);
696        assert_float_eq!(ret, -0.75);
697        ctx.borrow_mut().free();
698
699        // &sig1 on first output:
700        let (ctx, mut fun) = new_jit_fun(|bf| {
701            put_v(bf, 0, 0, 1, "value", "0.3");
702            put_v(bf, 0, 0, 2, "value", "-0.4");
703            put_n(bf, 0, 1, 1, "/%");
704            put_v(bf, 0, 2, 1, "set", "&sig1");
705        });
706
707        let (s1, _, ret) = fun.exec_2in_2out(0.0, 0.0);
708
709        assert_float_eq!(ret, 0.3);
710        assert_float_eq!(s1, -0.75);
711        ctx.borrow_mut().free();
712
713        // &sig1 on second output, but swapped outputs:
714        let (ctx, mut fun) = new_jit_fun(|bf| {
715            put_v(bf, 0, 0, 1, "value", "0.3");
716            put_v(bf, 0, 0, 2, "value", "-0.4");
717            put_n(bf, 0, 1, 1, "/%");
718            put_v(bf, 0, 2, 2, "set", "&sig1");
719            bf.shift_port(0, 1, 1, 0, true);
720        });
721
722        let (s1, _, ret) = fun.exec_2in_2out(0.0, 0.0);
723
724        assert_float_eq!(ret, 0.3);
725        assert_float_eq!(s1, -0.75);
726        ctx.borrow_mut().free();
727
728        // &sig1 on first output, but swapped outputs:
729        let (ctx, mut fun) = new_jit_fun(|bf| {
730            put_v(bf, 0, 0, 1, "value", "0.3");
731            put_v(bf, 0, 0, 2, "value", "-0.4");
732            put_n(bf, 0, 1, 1, "/%");
733            put_v(bf, 0, 2, 1, "set", "&sig1");
734            bf.shift_port(0, 1, 1, 0, true);
735        });
736
737        let (s1, _, ret) = fun.exec_2in_2out(0.0, 0.0);
738
739        assert_float_eq!(s1, 0.3);
740        assert_float_eq!(ret, -0.75);
741        ctx.borrow_mut().free();
742
743        // &sig1 on first output, but swapped inputs:
744        let (ctx, mut fun) = new_jit_fun(|bf| {
745            put_v(bf, 0, 0, 1, "value", "0.3");
746            put_v(bf, 0, 0, 2, "value", "-0.4");
747            put_n(bf, 0, 1, 1, "/%");
748            put_v(bf, 0, 2, 1, "set", "&sig1");
749            bf.shift_port(0, 1, 1, 0, false);
750        });
751
752        let (s1, _, ret) = fun.exec_2in_2out(0.0, 0.0);
753
754        assert_float_eq!(s1, -1.33333);
755        assert_float_eq!(ret, -0.1);
756        ctx.borrow_mut().free();
757
758        // &sig1 on first output, but swapped inputs and outputs:
759        let (ctx, mut fun) = new_jit_fun(|bf| {
760            put_v(bf, 0, 0, 1, "value", "0.3");
761            put_v(bf, 0, 0, 2, "value", "-0.4");
762            put_n(bf, 0, 1, 1, "/%");
763            put_v(bf, 0, 2, 1, "set", "&sig1");
764            bf.shift_port(0, 1, 1, 0, false);
765            bf.shift_port(0, 1, 1, 0, true);
766        });
767
768        let (s1, _, ret) = fun.exec_2in_2out(0.0, 0.0);
769
770        assert_float_eq!(ret, -1.33333);
771        assert_float_eq!(s1, -0.1);
772        ctx.borrow_mut().free();
773    }
774}