On Thu, Jun 19, 2025 at 12:13 PM Mikael Morin <morin-mik...@orange.fr> wrote:
>
> Le 18/06/2025 à 16:51, Richard Biener a écrit :
> > On Wed, Jun 18, 2025 at 11:23 AM Mikael Morin <morin-mik...@orange.fr> 
> > wrote:
> >>
> >> From: Mikael Morin <mik...@gcc.gnu.org>
> >>
> >> 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 = &<alloc00(12)>
> >>    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 <vector(2) char> [(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 <long unsigned int> [(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 <vector(2) long int> [(struct array01_character__kind_1_ *)&parm__3 
> >> + 32B] = { 1, 1 };
> >>      # parm__3.span = 1
> >>      # parm__3.dim[0].spacing = 1
> >>    MEM <vector(2) long int> [(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 <long unsigned int> [(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 <vector(2) long int> [(struct array01_integer__kind_4_ *)&atmp__4 + 
> >> 32B] = { 4, 4 };
> >>      # atmp__4.span = 4
> >>      # atmp__4.dim[0].spacing = 4
> >>    MEM <vector(2) long int> [(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
> program to keep track of the address and provide it to read or write in
> the storage area.
>
> > How do
> > you handle the case of an unknown value (see below, a symbolic
> > value might be a useful thing?)?
>
> It's basically an interpreter, so most values are known.  The two
> exceptions that are handled are uninitialised bits/variables, and
> pointers/addresses.  Uninitialised bits are tracked and propagated on
> copy.  Addresses are close to a symbolic value, they are represented as
> a reference to the storage area and an offset.
>
> > In that case external input
> > (when debugging an issue) would be useful.  Also for debugging running
> > the simulation after a specific pass would be nice - given duing main opts
> > not all functions are at the same pass position, it might be nice to be able
> > to run the simulation on GIMPLE IR as read from the GIMPLE frontend
> > (so you could -fdump-tree-<pass>-gimple and stitch together a harness).
>
> Actually, I have used it together with the gimple frontend.  The code is
> not mature enough to support every possible IL that may come out of any
> pass; the gimple frontend makes it possible to tweak the IL to
> circumvent some bugs or implementation holes easily.
>
> > Given you print an execution trace, and the pass issue, should this be
> > a dump modifier, aka -fdump-tree-<pass>-execution, and the trace amended
> > to the dump file (after the last function is processed, as said, there's no 
> > good
> > global state at all points)?
> >
> > I'll notice that we have some bits of "interpretation" around in
> > constant-folding,
> > crc-verification.cc (which has a limited symbolic execution engine),
> > tree-ssa-loop-niter.cc (loop_niter_by_eval).  Some kind of common 
> > abstraction
> > that centralizes the semantic of a stmt would be nice to have.
> >
> The simulator uses some of the constant-folding functions to get many
> TREE_CODE values supported for free.
> Regarding crc-verification and niter, they seem to target a very
> specific problem, so I'm not sure it's worth it.

Fair enough - most builtins should be handled via constant-folding,
you should be able to use gimple_fold_stmt_to_constant with providing
a valueization hook that will ask you for the value of SSA names referenced,
so if you keep a simple lattice this should work for most.

Richard.

>

Reply via email to