Le 18/06/2025 à 16:51, Richard Biener a écrit :
On Wed, Jun 18, 2025 at 11:23 AM Mikael Morin wrote:
From: Mikael Morin
Hello,
I'm proposing here an interpretor/simulator of the gimple IR.
It proved useful for me to debug complicated testcases, where
the misbehaviour is not obvious if you just look at the IR dump.
It produces an execution trace on the standard error stream, where
one can see the values of variables changing as statements are executed.
I only implemented the bits that were needed in my testcase(s), so there
are some holes in the implementation, especially regarding builtin
functions.
Here are two sample outputs:
a = {-2.0e+0, 3.0e+0, -5.0e+0, 7.0e+0, -1.1e+1, 1.3e+1};
# a[0] = -2.0e+0
# a[1] = 3.0e+0
# a[2] = -5.0e+0
# a[3] = 7.0e+0
# a[4] = -1.1e+1
# a[5] = 1.3e+1
b = {1.7e+1, -2.3e+1, 2.9e+1, -3.1e+1, 3.7e+1, -4.1e+1};
# b[0] = 1.7e+1
# b[1] = -2.3e+1
# b[2] = 2.9e+1
# b[3] = -3.1e+1
# b[4] = 3.7e+1
# b[5] = -4.1e+1
# Entering function main
# Executing bb 0
# Leaving bb 0, preparing to execute bb 2
# Executing bb 2
_gfortran_set_args (argc_1(D), argv_2(D));
# ignored
_gfortran_set_options (7, &options__7[0]);
# ignored
_3 = __builtin_calloc (12, 1);
# _3 = &
if (_3 == 0B)
# Condition evaluates to false
# Leaving bb 2, preparing to execute bb 4
__var_3_do_19 = PHI <0(2), _17(5)>
# __var_3_do_19 = 0
_18 = PHI <0.0(2), _5(5)>
# _18 = 0.0
# Executing bb 4
_17 = __var_3_do_19 + 1;
# _17 = 1
_14 = (long unsigned int) _17;
# _14 = 1
_13 = MEM[(real__kind_4_ *)&a + -4B + _14 * 4];
# _13 = -2.0e+0
_12 = _13 * 2.9e+1;
# _12 = -5.8e+1
_11 = _12 + _18;
# _11 = -5.8e+1
MEM[(real__kind_4_ *)_3 + -4B + _14 * 4] = _11;
# MEM[(real__kind_4_ *)_3 + -4B + _14 * 4] = -5.8e+1
if (_17 == 3)
# Condition evaluates to false
# Leaving bb 4, preparing to execute bb 5
# Executing bb 5
_5 = MEM[(real__kind_4_ *)_3 + _14 * 4];
# _5 = 0.0
# Leaving bb 5, preparing to execute bb 4
__var_3_do_19 = PHI <0(2), _17(5)>
# __var_3_do_19 = 1
_18 = PHI <0.0(2), _5(5)>
# _18 = 0.0
# Executing bb 4
_17 = __var_3_do_19 + 1;
# _17 = 2
_14 = (long unsigned int) _17;
# _14 = 2
_13 = MEM[(real__kind_4_ *)&a + -4B + _14 * 4];
# _13 = 3.0e+0
_12 = _13 * 2.9e+1;
# _12 = 8.7e+1
_11 = _12 + _18;
# _11 = 8.7e+1
MEM[(real__kind_4_ *)_3 + -4B + _14 * 4] = _11;
# MEM[(real__kind_4_ *)_3 + -4B + _14 * 4] = 8.7e+1
if (_17 == 3)
# Condition evaluates to false
# Leaving bb 4, preparing to execute bb 5
# Executing bb 5
_5 = MEM[(real__kind_4_ *)_3 + _14 * 4];
# _5 = 0.0
# Leaving bb 5, preparing to execute bb 4
__var_3_do_19 = PHI <0(2), _17(5)>
# __var_3_do_19 = 2
_18 = PHI <0.0(2), _5(5)>
# _18 = 0.0
# Executing bb 4
...
MEM [(character__kind_1_ *)&str] = { 97, 99 };
# str[0][0] = 97
# str[1][0] = 99
str[2][0] = 97;
# str[2][0] = 97
parm__3.data = &str;
# parm__3.data = &str
parm__3.offset = -1;
# parm__3.offset = -1
parm__3.dtype.elem_len = 1;
# parm__3.dtype.elem_len = 1
MEM [(void *)&parm__3 + 24B] = 6601364733952;
# parm__3.dtype.version = 0
# parm__3.dtype.rank = 1
# parm__3.dtype.type = 6
# parm__3.dtype.attribute = 0
MEM [(struct array01_character__kind_1_ *)&parm__3 +
32B] = { 1, 1 };
# parm__3.span = 1
# parm__3.dim[0].spacing = 1
MEM [(struct array01_character__kind_1_ *)&parm__3 +
48B] = { 1, 3 };
# parm__3.dim[0].lbound = 1
# parm__3.dim[0].ubound = 3
atmp__4.offset = 0;
# atmp__4.offset = 0
atmp__4.dtype.elem_len = 4;
# atmp__4.dtype.elem_len = 4
MEM [(void *)&atmp__4 + 24B] = 1103806595072;
# atmp__4.dtype.version = 0
# atmp__4.dtype.rank = 1
# atmp__4.dtype.type = 1
# atmp__4.dtype.attribute = 0
MEM [(struct array01_integer__kind_4_ *)&atmp__4 + 32B]
= { 4, 4 };
# atmp__4.span = 4
# atmp__4.dim[0].spacing = 4
MEM [(struct array01_integer__kind_4_ *)&atmp__4 + 48B]
= { 0, 0 };
# atmp__4.dim[0].lbound = 0
# atmp__4.dim[0].ubound = 0
atmp__4.data = &A__5;
# atmp__4.data = &A__5
...
Is anyone interested in integrating this into mainline?
Thoughts, comments?
Nice. Can you expand a bit on how you model global state?
Do you mean compiler global state?
Well, I don't know, I don't model anything, nor do I know what there
would be to model.
If you mean in the user program, global variables and memory allocation
are modelled. global variables are in a special root scope, that is
parent of the main function scope; thus they are reachable from every
function scope. Memory allocation is modelled the obvious way; new
storage areas without variable attached to them are created dynamically
as specific implementation of __builtin_malloc. It is up to the user
pro