working on add[o][.] implementation
[power-instruction-analyzer.git] / src / lib.rs
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 // See Notices.txt for copyright information
3
4 #![cfg_attr(feature = "native_instrs", feature(llvm_asm))]
5
6 #[cfg(all(feature = "native_instrs", not(target_arch = "powerpc64")))]
7 compile_error!("native_instrs feature requires target_arch to be powerpc64");
8
9 pub mod instr_models;
10 mod serde_hex;
11
12 use serde::{Deserialize, Serialize};
13 use std::{
14 cmp::Ordering,
15 ops::{Index, IndexMut},
16 };
17
18 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
19 pub struct OverflowFlags {
20 pub so: bool,
21 pub ov: bool,
22 pub ov32: bool,
23 }
24
25 impl OverflowFlags {
26 pub const fn from_xer(xer: u64) -> Self {
27 Self {
28 so: (xer & 0x8000_0000) != 0,
29 ov: (xer & 0x4000_0000) != 0,
30 ov32: (xer & 0x8_0000) != 0,
31 }
32 }
33 pub const fn from_overflow(overflow: bool) -> Self {
34 Self {
35 so: overflow,
36 ov: overflow,
37 ov32: overflow,
38 }
39 }
40 }
41
42 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
43 pub struct ConditionRegister {
44 pub lt: bool,
45 pub gt: bool,
46 pub eq: bool,
47 pub so: bool,
48 }
49
50 impl ConditionRegister {
51 pub const fn from_4_bits(bits: u8) -> Self {
52 // assert bits is 4-bits long
53 // can switch to using assert! once rustc feature const_panic is stabilized
54 [0; 0x10][bits as usize];
55
56 Self {
57 lt: (bits & 8) != 0,
58 gt: (bits & 4) != 0,
59 eq: (bits & 2) != 0,
60 so: (bits & 1) != 0,
61 }
62 }
63 pub const CR_FIELD_COUNT: usize = 8;
64 pub const fn from_cr_field(cr: u32, field_index: usize) -> Self {
65 // assert field_index is less than CR_FIELD_COUNT
66 // can switch to using assert! once rustc feature const_panic is stabilized
67 [0; Self::CR_FIELD_COUNT][field_index];
68
69 let reversed_field_index = Self::CR_FIELD_COUNT - field_index - 1;
70 let bits = (cr >> (4 * reversed_field_index)) & 0xF;
71 Self::from_4_bits(bits as u8)
72 }
73 pub fn from_signed_int<T: Ord + Default>(value: T, so: bool) -> Self {
74 let ordering = value.cmp(&T::default());
75 Self {
76 lt: ordering == Ordering::Less,
77 gt: ordering == Ordering::Greater,
78 eq: ordering == Ordering::Equal,
79 so,
80 }
81 }
82 }
83
84 #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
85 pub struct InstructionResult {
86 #[serde(
87 default,
88 skip_serializing_if = "Option::is_none",
89 with = "serde_hex::SerdeHex"
90 )]
91 pub rt: Option<u64>,
92 #[serde(default, flatten, skip_serializing_if = "Option::is_none")]
93 pub overflow: Option<OverflowFlags>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub cr0: Option<ConditionRegister>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub cr1: Option<ConditionRegister>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub cr2: Option<ConditionRegister>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub cr3: Option<ConditionRegister>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub cr4: Option<ConditionRegister>,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub cr5: Option<ConditionRegister>,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub cr6: Option<ConditionRegister>,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub cr7: Option<ConditionRegister>,
110 }
111
112 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
113 pub enum InstructionInputRegister {
114 #[serde(rename = "ra")]
115 Ra,
116 #[serde(rename = "rb")]
117 Rb,
118 #[serde(rename = "rc")]
119 Rc,
120 }
121
122 #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
123 pub struct InstructionInput {
124 #[serde(with = "serde_hex::SerdeHex")]
125 pub ra: u64,
126 #[serde(with = "serde_hex::SerdeHex")]
127 pub rb: u64,
128 #[serde(with = "serde_hex::SerdeHex")]
129 pub rc: u64,
130 }
131
132 impl Index<InstructionInputRegister> for InstructionInput {
133 type Output = u64;
134 fn index(&self, index: InstructionInputRegister) -> &Self::Output {
135 match index {
136 InstructionInputRegister::Ra => &self.ra,
137 InstructionInputRegister::Rb => &self.rb,
138 InstructionInputRegister::Rc => &self.rc,
139 }
140 }
141 }
142
143 impl IndexMut<InstructionInputRegister> for InstructionInput {
144 fn index_mut(&mut self, index: InstructionInputRegister) -> &mut Self::Output {
145 match index {
146 InstructionInputRegister::Ra => &mut self.ra,
147 InstructionInputRegister::Rb => &mut self.rb,
148 InstructionInputRegister::Rc => &mut self.rc,
149 }
150 }
151 }
152
153 fn is_false(v: &bool) -> bool {
154 !v
155 }
156
157 #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
158 pub struct TestCase {
159 pub instr: Instr,
160 #[serde(flatten)]
161 pub inputs: InstructionInput,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub native_outputs: Option<InstructionResult>,
164 pub model_outputs: InstructionResult,
165 #[serde(default, skip_serializing_if = "is_false")]
166 pub model_mismatch: bool,
167 }
168
169 #[derive(Clone, Debug, Serialize, Deserialize)]
170 pub struct WholeTest {
171 #[serde(default, skip_serializing_if = "Vec::is_empty")]
172 pub test_cases: Vec<TestCase>,
173 pub any_model_mismatch: bool,
174 }
175
176 #[cfg(feature = "native_instrs")]
177 macro_rules! map_instr_asm_args {
178 ([], [], []) => {
179 ""
180 };
181 ([], [], [$string0:literal $($strings:literal)*]) => {
182 concat!(" ", $string0, $(", ", $strings),*)
183 };
184 ([$($args:ident)*], [rt $($results:ident)*], [$($strings:literal)*]) => {
185 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)* "$0"])
186 };
187 ([ra $($args:ident)*], [$($results:ident)*], [$($strings:literal)*]) => {
188 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)* "$3"])
189 };
190 ([rb $($args:ident)*], [$($results:ident)*], [$($strings:literal)*]) => {
191 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)* "$4"])
192 };
193 ([rc $($args:ident)*], [$($results:ident)*], [$($strings:literal)*]) => {
194 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)* "$5"])
195 };
196 ([$($args:ident)*], [ov $($results:ident)*], [$($strings:literal)*]) => {
197 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
198 };
199 ([$($args:ident)*], [cr0 $($results:ident)*], [$($strings:literal)*]) => {
200 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
201 };
202 ([$($args:ident)*], [cr1 $($results:ident)*], [$($strings:literal)*]) => {
203 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
204 };
205 ([$($args:ident)*], [cr2 $($results:ident)*], [$($strings:literal)*]) => {
206 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
207 };
208 ([$($args:ident)*], [cr3 $($results:ident)*], [$($strings:literal)*]) => {
209 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
210 };
211 ([$($args:ident)*], [cr4 $($results:ident)*], [$($strings:literal)*]) => {
212 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
213 };
214 ([$($args:ident)*], [cr5 $($results:ident)*], [$($strings:literal)*]) => {
215 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
216 };
217 ([$($args:ident)*], [cr6 $($results:ident)*], [$($strings:literal)*]) => {
218 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
219 };
220 ([$($args:ident)*], [cr7 $($results:ident)*], [$($strings:literal)*]) => {
221 map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*])
222 };
223 }
224
225 macro_rules! map_instr_input_registers {
226 ([], [$($reg:expr,)*]) => {
227 [$($reg,)*]
228 };
229 ([ra $($args:ident)*], [$($reg:expr,)*]) => {
230 map_instr_input_registers!([$($args)*], [InstructionInputRegister::Ra, $($reg,)*])
231 };
232 ([rb $($args:ident)*], [$($reg:expr,)*]) => {
233 map_instr_input_registers!([$($args)*], [InstructionInputRegister::Rb, $($reg,)*])
234 };
235 ([rc $($args:ident)*], [$($reg:expr,)*]) => {
236 map_instr_input_registers!([$($args)*], [InstructionInputRegister::Rc, $($reg,)*])
237 };
238 }
239
240 #[cfg(feature = "native_instrs")]
241 macro_rules! map_instr_results {
242 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, []) => {};
243 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [rt $($args:ident)*]) => {
244 $retval.rt = Some($rt);
245 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
246 };
247 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [ov $($args:ident)*]) => {
248 $retval.overflow = Some(OverflowFlags::from_xer($xer));
249 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
250 };
251 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr0 $($args:ident)*]) => {
252 $retval.cr0 = Some(ConditionRegister::from_cr_field($cr, 0));
253 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
254 };
255 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr1 $($args:ident)*]) => {
256 $retval.cr1 = Some(ConditionRegister::from_cr_field($cr, 1));
257 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
258 };
259 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr2 $($args:ident)*]) => {
260 $retval.cr2 = Some(ConditionRegister::from_cr_field($cr, 2));
261 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
262 };
263 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr3 $($args:ident)*]) => {
264 $retval.cr3 = Some(ConditionRegister::from_cr_field($cr, 3));
265 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
266 };
267 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr4 $($args:ident)*]) => {
268 $retval.cr4 = Some(ConditionRegister::from_cr_field($cr, 4));
269 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
270 };
271 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr5 $($args:ident)*]) => {
272 $retval.cr5 = Some(ConditionRegister::from_cr_field($cr, 5));
273 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
274 };
275 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr6 $($args:ident)*]) => {
276 $retval.cr6 = Some(ConditionRegister::from_cr_field($cr, 6));
277 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
278 };
279 ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr7 $($args:ident)*]) => {
280 $retval.cr7 = Some(ConditionRegister::from_cr_field($cr, 7));
281 map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]);
282 };
283 }
284
285 #[cfg(feature = "native_instrs")]
286 macro_rules! instr {
287 (
288 #[enumerant = $enumerant:ident]
289 fn $fn:ident($($args:ident),*) -> ($($results:ident),*) {
290 $instr:literal
291 }
292 ) => {
293 pub fn $fn(inputs: InstructionInput) -> InstructionResult {
294 #![allow(unused_variables, unused_assignments)]
295 let InstructionInput {
296 ra,
297 rb,
298 rc,
299 } = inputs;
300 let rt: u64;
301 let xer: u64;
302 let cr: u32;
303 unsafe {
304 llvm_asm!(
305 concat!(
306 "mfxer $1\n",
307 "and $1, $1, $7\n",
308 "mtxer $1\n",
309 $instr, " ",
310 map_instr_asm_args!([$($args)*], [$($results)*], []),
311 "\n",
312 "mfxer $1\n",
313 "mfcr $2\n",
314 )
315 : "=&b"(rt), "=&b"(xer), "=&b"(cr)
316 : "b"(ra), "b"(rb), "b"(rc), "b"(0u64), "b"(!0x8000_0000u64)
317 : "xer", "cr");
318 }
319 let mut retval = InstructionResult::default();
320 map_instr_results!(rt, xer, cr, retval, [$($results)*]);
321 retval
322 }
323 };
324 }
325
326 macro_rules! instrs {
327 (
328 $(
329 #[enumerant = $enumerant:ident]
330 fn $fn:ident($($args:ident),*) -> ($($results:ident),*) {
331 $instr:literal
332 }
333 )+
334 ) => {
335 #[cfg(feature = "python")]
336 macro_rules! wrap_all_instr_fns {
337 ($m:ident) => {
338 wrap_instr_fns! {
339 #![pymodule($m)]
340
341 $(fn $fn(inputs: InstructionInput) -> InstructionResult;)*
342 }
343 };
344 }
345
346 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
347 pub enum Instr {
348 $(
349 #[serde(rename = $instr)]
350 $enumerant,
351 )+
352 }
353
354 impl Instr {
355 #[cfg(feature = "native_instrs")]
356 pub fn get_native_fn(self) -> fn(InstructionInput) -> InstructionResult {
357 match self {
358 $(
359 Self::$enumerant => native_instrs::$fn,
360 )+
361 }
362 }
363 pub fn get_model_fn(self) -> fn(InstructionInput) -> InstructionResult {
364 match self {
365 $(
366 Self::$enumerant => instr_models::$fn,
367 )+
368 }
369 }
370 pub fn get_used_input_registers(self) -> &'static [InstructionInputRegister] {
371 match self {
372 $(
373 Self::$enumerant => &map_instr_input_registers!([$($args)*], []),
374 )+
375 }
376 }
377 pub fn name(self) -> &'static str {
378 match self {
379 $(
380 Self::$enumerant => $instr,
381 )+
382 }
383 }
384 pub const VALUES: &'static [Self] = &[
385 $(
386 Self::$enumerant,
387 )+
388 ];
389 }
390
391 #[cfg(feature = "native_instrs")]
392 pub mod native_instrs {
393 use super::*;
394
395 $(
396 instr! {
397 #[enumerant = $enumerant]
398 fn $fn($($args),*) -> ($($results),*) {
399 $instr
400 }
401 }
402 )+
403 }
404 };
405 }
406
407 instrs! {
408 // add
409 #[enumerant = Add]
410 fn add(ra, rb) -> (rt) {
411 "add"
412 }
413 #[enumerant = AddO]
414 fn addo(ra, rb) -> (rt, ov) {
415 "addo"
416 }
417 #[enumerant = Add_]
418 fn add_(ra, rb) -> (rt, cr0) {
419 "add."
420 }
421 #[enumerant = AddO_]
422 fn addo_(ra, rb) -> (rt, ov, cr0) {
423 "addo."
424 }
425
426 // divde
427 #[enumerant = DivDE]
428 fn divde(ra, rb) -> (rt) {
429 "divde"
430 }
431 #[enumerant = DivDEO]
432 fn divdeo(ra, rb) -> (rt, ov) {
433 "divdeo"
434 }
435 #[enumerant = DivDE_]
436 fn divde_(ra, rb) -> (rt, cr0) {
437 "divde."
438 }
439 #[enumerant = DivDEO_]
440 fn divdeo_(ra, rb) -> (rt, ov, cr0) {
441 "divdeo."
442 }
443
444 // divdeu
445 #[enumerant = DivDEU]
446 fn divdeu(ra, rb) -> (rt) {
447 "divdeu"
448 }
449 #[enumerant = DivDEUO]
450 fn divdeuo(ra, rb) -> (rt, ov) {
451 "divdeuo"
452 }
453 #[enumerant = DivDEU_]
454 fn divdeu_(ra, rb) -> (rt, cr0) {
455 "divdeu."
456 }
457 #[enumerant = DivDEUO_]
458 fn divdeuo_(ra, rb) -> (rt, ov, cr0) {
459 "divdeuo."
460 }
461
462 // divd
463 #[enumerant = DivD]
464 fn divd(ra, rb) -> (rt) {
465 "divd"
466 }
467 #[enumerant = DivDO]
468 fn divdo(ra, rb) -> (rt, ov) {
469 "divdo"
470 }
471 #[enumerant = DivD_]
472 fn divd_(ra, rb) -> (rt, cr0) {
473 "divd."
474 }
475 #[enumerant = DivDO_]
476 fn divdo_(ra, rb) -> (rt, ov, cr0) {
477 "divdo."
478 }
479
480 // divdu
481 #[enumerant = DivDU]
482 fn divdu(ra, rb) -> (rt) {
483 "divdu"
484 }
485 #[enumerant = DivDUO]
486 fn divduo(ra, rb) -> (rt, ov) {
487 "divduo"
488 }
489 #[enumerant = DivDU_]
490 fn divdu_(ra, rb) -> (rt, cr0) {
491 "divdu."
492 }
493 #[enumerant = DivDUO_]
494 fn divduo_(ra, rb) -> (rt, ov, cr0) {
495 "divduo."
496 }
497
498 // divwe
499 #[enumerant = DivWE]
500 fn divwe(ra, rb) -> (rt) {
501 "divwe"
502 }
503 #[enumerant = DivWEO]
504 fn divweo(ra, rb) -> (rt, ov) {
505 "divweo"
506 }
507 #[enumerant = DivWE_]
508 fn divwe_(ra, rb) -> (rt, cr0) {
509 "divwe."
510 }
511 #[enumerant = DivWEO_]
512 fn divweo_(ra, rb) -> (rt, ov, cr0) {
513 "divweo."
514 }
515
516 // divweu
517 #[enumerant = DivWEU]
518 fn divweu(ra, rb) -> (rt) {
519 "divweu"
520 }
521 #[enumerant = DivWEUO]
522 fn divweuo(ra, rb) -> (rt, ov) {
523 "divweuo"
524 }
525 #[enumerant = DivWEU_]
526 fn divweu_(ra, rb) -> (rt, cr0) {
527 "divweu."
528 }
529 #[enumerant = DivWEUO_]
530 fn divweuo_(ra, rb) -> (rt, ov, cr0) {
531 "divweuo."
532 }
533
534 // divw
535 #[enumerant = DivW]
536 fn divw(ra, rb) -> (rt) {
537 "divw"
538 }
539 #[enumerant = DivWO]
540 fn divwo(ra, rb) -> (rt, ov) {
541 "divwo"
542 }
543 #[enumerant = DivW_]
544 fn divw_(ra, rb) -> (rt, cr0) {
545 "divw."
546 }
547 #[enumerant = DivWO_]
548 fn divwo_(ra, rb) -> (rt, ov, cr0) {
549 "divwo."
550 }
551
552 // divwu
553 #[enumerant = DivWU]
554 fn divwu(ra, rb) -> (rt) {
555 "divwu"
556 }
557 #[enumerant = DivWUO]
558 fn divwuo(ra, rb) -> (rt, ov) {
559 "divwuo"
560 }
561 #[enumerant = DivWU_]
562 fn divwu_(ra, rb) -> (rt, cr0) {
563 "divwu."
564 }
565 #[enumerant = DivWUO_]
566 fn divwuo_(ra, rb) -> (rt, ov, cr0) {
567 "divwuo."
568 }
569
570 // mod*
571 #[enumerant = ModSD]
572 fn modsd(ra, rb) -> (rt) {
573 "modsd"
574 }
575 #[enumerant = ModUD]
576 fn modud(ra, rb) -> (rt) {
577 "modud"
578 }
579 #[enumerant = ModSW]
580 fn modsw(ra, rb) -> (rt) {
581 "modsw"
582 }
583 #[enumerant = ModUW]
584 fn moduw(ra, rb) -> (rt) {
585 "moduw"
586 }
587
588 // mullw
589 #[enumerant = MulLW]
590 fn mullw(ra, rb) -> (rt) {
591 "mullw"
592 }
593 #[enumerant = MulLWO]
594 fn mullwo(ra, rb) -> (rt, ov) {
595 "mullwo"
596 }
597 #[enumerant = MulLW_]
598 fn mullw_(ra, rb) -> (rt, cr0) {
599 "mullw."
600 }
601 #[enumerant = MulLWO_]
602 fn mullwo_(ra, rb) -> (rt, ov, cr0) {
603 "mullwo."
604 }
605
606 // mulhw
607 #[enumerant = MulHW]
608 fn mulhw(ra, rb) -> (rt) {
609 "mulhw"
610 }
611 #[enumerant = MulHW_]
612 fn mulhw_(ra, rb) -> (rt, cr0) {
613 "mulhw."
614 }
615
616 // mulhwu
617 #[enumerant = MulHWU]
618 fn mulhwu(ra, rb) -> (rt) {
619 "mulhwu"
620 }
621 #[enumerant = MulHWU_]
622 fn mulhwu_(ra, rb) -> (rt, cr0) {
623 "mulhwu."
624 }
625
626 // mulld
627 #[enumerant = MulLD]
628 fn mulld(ra, rb) -> (rt) {
629 "mulld"
630 }
631 #[enumerant = MulLDO]
632 fn mulldo(ra, rb) -> (rt, ov) {
633 "mulldo"
634 }
635 #[enumerant = MulLD_]
636 fn mulld_(ra, rb) -> (rt, cr0) {
637 "mulld."
638 }
639 #[enumerant = MulLDO_]
640 fn mulldo_(ra, rb) -> (rt, ov, cr0) {
641 "mulldo."
642 }
643
644 // mulhd
645 #[enumerant = MulHD]
646 fn mulhd(ra, rb) -> (rt) {
647 "mulhd"
648 }
649 #[enumerant = MulHD_]
650 fn mulhd_(ra, rb) -> (rt, cr0) {
651 "mulhd."
652 }
653
654 // mulhdu
655 #[enumerant = MulHDU]
656 fn mulhdu(ra, rb) -> (rt) {
657 "mulhdu"
658 }
659 #[enumerant = MulHDU_]
660 fn mulhdu_(ra, rb) -> (rt, cr0) {
661 "mulhdu."
662 }
663
664 // madd*
665 #[enumerant = MAddHD]
666 fn maddhd(ra, rb, rc) -> (rt) {
667 "maddhd"
668 }
669 #[enumerant = MAddHDU]
670 fn maddhdu(ra, rb, rc) -> (rt) {
671 "maddhdu"
672 }
673 #[enumerant = MAddLD]
674 fn maddld(ra, rb, rc) -> (rt) {
675 "maddld"
676 }
677 }
678
679 // must be after instrs macro call since it uses a macro definition
680 mod python;