Skip to content

Conversation

@DrMichaelPetter
Copy link
Collaborator

Closes #1494 by providing a domain lifter, that applies meet until the gas depletes. In a first application, we get a new parameter ana.apron.narrowing_gas to specify the amount of gas per state, which comes in handy for poyhedra-based analysis.

// SKIP PARAM: --set ana.apron.narrowing_gas 1 --set ana.activated[+] apron --set ana.apron.domain polyhedra
int loop(){
  int i, j, k;
  int a = 0;
  for (i = 500; i >= 1; i--)
  {
    a++;
    __goblint_check(a + i - 501 == 0); // needs 1x narrowing or octagons
  }
  return 0;
}

@DrMichaelPetter DrMichaelPetter self-assigned this Nov 21, 2025
@DrMichaelPetter DrMichaelPetter added feature precision relational Relational analyses (Apron, affeq, lin2var) labels Nov 21, 2025
@sim642 sim642 self-requested a review November 21, 2025 10:09
Comment on lines +18 to +36
let narrowing_gas = GobConfig.get_int "ana.apron.narrowing_gas"
in
if (narrowing_gas > 0) then
let module Narrowed =
struct
module PolyhedraChainParams: Printable.ChainParams =
struct
let n () = narrowing_gas
let names = string_of_int
end
include NarrowingGas.DLifter (Spec) (PolyhedraChainParams)
module A = Spec.A
let access = Spec.access
let name = Spec.name
end
in
(module Narrowed)
else
(module Spec)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in a slightly less ideal place than I imagined in #1494, but I can see why and it is fine.

Ideally, I'd like to just wrap this lifting around polyhedra, but not other Apron domains, somewhere in ApronDomain, and have the analysis be completely oblivious to the fact.
Because in some sense it's "just" a lifting of any Lattice.S → Lattice.S. But that's only so if things are fully properly abstract, but our RelationDomain/ApronDomain setup is far from that. So it'd require a large cleanup/refactoring to be able to do it in my dream way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's is purely a lattice -> lattice functor, as the gas needs to be reset at every transfer function, no?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the NarrowingGas.Dom functor here (based on WideningDelay, etc) is essentially just that. It's the act of unlift and lift to do the operation that leads to the reset. And that's what NarrowingGas.DLifter used here exactly does.

It additionally needs all relational domain operations (instead of Spec operations) lifted, which needs more boilerplate. And it won't really gain anything from Goblint usage perspective.

@DrMichaelPetter
Copy link
Collaborator Author

DrMichaelPetter commented Nov 21, 2025

There is some strange influence between apron w. narrow gas 1 and def_exc going on, when the loopUnrollHeuristics is on:

//PARAMS:  --set ana.apron.narrowing_gas 1 --set ana.activated[+] apron --set ana.apron.domain polyhedra --set ana.autotune.enabled true --set ana.autotune.activated '["loopUnrollHeuristic"]' 
int main() {
    int x;
    int y;
    int xtmp;
    if (y >= 0) 
    {
        xtmp = x;
        while(xtmp>y) {
            xtmp = xtmp - y;
        }
        y = xtmp;
    }
    return 0;
}

Goblint does not reach a fixpoint in this configuration, with a diff for
xtmp = (Unknown int([-31,31])) instead of (Not {0}([0,31])) at the inner loop guard.

@sim642
Copy link
Member

sim642 commented Nov 21, 2025

With --trace sol --trace sol2 --trace solchange you can see the last iteration (immediately after the narrow by meet) of that unknown:

%%% sol: Var: L:node 27 "xtmp > y" on narrowing-gas-fp-error.c:9:15-9:21 (wp: true)
Old value: HConsed lifted PathSensitive (ProjectiveSet (MCP.D * map)):PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(),
                                                                                                                           mallocWrapper:(wrapper call:Unknown node, unique calls:{}),
                                                                                                                           base:({
                                                                                                                                   Local {
                                                                                                                                     x ->   ⊤
                                                                                                                                     y ->   (Unknown int([0,31]))
                                                                                                                                     xtmp ->   (Not {0}([0,31]))
                                                                                                                                   }
                                                                                                                                 }, {}, {}, {}),
                                                                                                                           threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)),
                                                                                                                           threadflag:Singlethreaded,
                                                                                                                           threadreturn:true,
                                                                                                                           escape:{},
                                                                                                                           mutexEvents:(),
                                                                                                                           access:(),
                                                                                                                           mutex:(lockset:{}, multiplicity:{}),
                                                                                                                           race:(),
                                                                                                                           mhp:(),
                                                                                                                           assert:(),
                                                                                                                           pthreadMutexType:(),
                                                                                                                           apron:(Apron Polyhedra * Unit:([|-x#297+2147483647>=0; -xtmp#299+2147483647>=0; xtmp#299-1>=0; xtmp#299+y#298-1>=0|] (env: [|0> x#297:int; 1> xtmp#299:int; 2> y#298:int|]), ()), chain:1)], map:{})}
Eqd: HConsed lifted PathSensitive (ProjectiveSet (MCP.D * map)):PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(),
                                                                                                                     mallocWrapper:(wrapper call:Unknown node, unique calls:{}),
                                                                                                                     base:({
                                                                                                                             Local {
                                                                                                                               x ->   ⊤
                                                                                                                               y ->   (Unknown int([0,31]))
                                                                                                                               xtmp ->   ⊤
                                                                                                                             }
                                                                                                                           }, {}, {}, {}),
                                                                                                                     threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)),
                                                                                                                     threadflag:Singlethreaded,
                                                                                                                     threadreturn:true,
                                                                                                                     escape:{},
                                                                                                                     mutexEvents:(),
                                                                                                                     access:(),
                                                                                                                     mutex:(lockset:{}, multiplicity:{}),
                                                                                                                     race:(),
                                                                                                                     mhp:(),
                                                                                                                     assert:(),
                                                                                                                     pthreadMutexType:(),
                                                                                                                     apron:(Apron Polyhedra * Unit:([|-x#297+2147483647>=0; -xtmp#299-y#298+2147483647>=0; -xtmp#299+2147483647>=0; xtmp#299-1>=0; xtmp#299+2y#298-1>=0|] (env: [|0> x#297:int; 1> xtmp#299:int; 2> y#298:int|]), ()), chain:0)], map:{})}

For some reason the right-hand side for the loop head is not monotonic? Or somehow with improved apron abstraction, there's less precise xtmp in base when reevaluated.

In particular, with more tracing, Apron doesn't seem to return a lower bound 1 which it seems to have:

        %%% evalint: (narrowing-gas-fp-error.c:10:13-10:28)  relation query xtmp - y (MinusA(Lval(Var(xtmp, NoOffset)), Lval(Var(y, NoOffset))))
          %%% evalint: relation st: ([|-x#297+2147483647>=0; -xtmp#299+2147483647>=0; xtmp#299-y#298-1>=0; xtmp#299+y#298-1>=0|] (env: [|0> x#297:int; 1> xtmp#299:int; 2> y#298:int|]), ())
          %%% relation: eval_int: exp_is_constraint MinusA(Lval(Var(xtmp, NoOffset)), Lval(Var(y, NoOffset))) = false
          %%% relation: texpr1_expr_of_cil_exp/simplify: Lval(Var(y, NoOffset)) -> (Unknown int([0,31]))
          %%% relation: texpr1_expr_of_cil_exp/simplify: Lval(Var(xtmp, NoOffset)) -> (Not {0}([0,31]))
          %%% relation: texpr1_expr_of_cil_exp: MinusA(Lval(Var(xtmp, NoOffset)), Lval(Var(y, NoOffset))) -> xtmp#299 -_i,n y#298 (true)
          %%% evalint: relation query xtmp - y -> IntDomLifter(intdomtuple):⊤

@DrMichaelPetter
Copy link
Collaborator Author

Here is a more simplified version of the same thing without autotuner -- so definitely a pure apron/polyhedra and narrowing thing:

// SKIP PARAM --set ana.apron.narrowing_gas 1 --set ana.activated[+] apron --set ana.apron.domain polyhedra
int main(void)
{
  int x;
  int y;
  int xtmp;
  if (y < 0) return 0;
  if (x <= y) return 0;
  xtmp = x-y;
  while (xtmp > y)
  {
      xtmp -= y;
  }
  return 0;
}

@DrMichaelPetter
Copy link
Collaborator Author

In particular, with more tracing, Apron doesn't seem to return a lower bound 1 which it seems to have:

        %%% evalint: (narrowing-gas-fp-error.c:10:13-10:28)  relation query xtmp - y (MinusA(Lval(Var(xtmp, NoOffset)), Lval(Var(y, NoOffset))))
          %%% evalint: relation st: ([|-x#297+2147483647>=0; -xtmp#299+2147483647>=0; xtmp#299-y#298-1>=0; xtmp#299+y#298-1>=0|] (env: [|0> x#297:int; 1> xtmp#299:int; 2> y#298:int|]), ())
          %%% relation: eval_int: exp_is_constraint MinusA(Lval(Var(xtmp, NoOffset)), Lval(Var(y, NoOffset))) = false
          %%% relation: texpr1_expr_of_cil_exp/simplify: Lval(Var(y, NoOffset)) -> (Unknown int([0,31]))
          %%% relation: texpr1_expr_of_cil_exp/simplify: Lval(Var(xtmp, NoOffset)) -> (Not {0}([0,31]))
          %%% relation: texpr1_expr_of_cil_exp: MinusA(Lval(Var(xtmp, NoOffset)), Lval(Var(y, NoOffset))) -> xtmp#299 -_i,n y#298 (true)
          %%% evalint: relation query xtmp - y -> IntDomLifter(intdomtuple):⊤

It seems to get lost during a sharedFunctions.eval_int while converting [1, 4294967293] by aprons bound_texpr into an interval via ID.of_interval

@sim642
Copy link
Member

sim642 commented Nov 22, 2025

The upper bound 4294967293 overflows int, which is probably why it goes to top. Maybe with sem.int.signed_overflow being assume_none it'd behave differently?

Either way, there's something strange because there should be a less precise state pre-narrowing, yet it somehow has a better upper bound to avoid overflowing to top.

@DrMichaelPetter
Copy link
Collaborator Author

The upper bound 4294967293 overflows int, which is probably why it goes to top. Maybe with sem.int.signed_overflow being assume_none it'd behave differently?

It behaves the same.

Either way, there's something strange because there should be a less precise state pre-narrowing, yet it somehow has a better upper bound to avoid overflowing to top.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature precision relational Relational analyses (Apron, affeq, lin2var)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Narrowing by fixed number of meets

4 participants