-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bitfield Domain #1623
base: master
Are you sure you want to change the base?
Bitfield Domain #1623
Conversation
…d some revision. Possible side-effects and runtime in O(n^2) while O(n) should be possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semgrep OSS found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
…act bitfield by eliminating constants that would result in a shift to zero beforehand
* implemented modulo * current changes * implementation of add, sub, mul based on paper * implemented neg * bug fixes for arith operators --------- Co-authored-by: Adrian Krauß <[email protected]>
Overflow and wrapping
Bugfixes from benchmarking
let a', b' = ID.meet a (ID.logand a c), ID.meet b (ID.logand b c) in | ||
let (cz, co) = ID.to_bitfield ikind c in | ||
let (az, ao) = ID.to_bitfield ikind a' in | ||
let (bz, bo) = ID.to_bitfield ikind b' in | ||
let cDef1 = Z.logand co (Z.lognot cz) in | ||
let aDef0 = Z.logand az (Z.lognot ao) in | ||
let bDef0 = Z.logand bz (Z.lognot bo) in | ||
let az = Z.logand az (Z.lognot (Z.logand bDef0 cDef1)) in | ||
let bz = Z.logand bz (Z.lognot (Z.logand aDef0 cDef1)) in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should most of these computations not be moved inside your domain? (After getting the bitfields).
This works on the internal representation of these bitfield things, so maybe should be in there. Granted, the intervals also leak outside of their domain,but people intuitively understand how they work.
Also, please leave some comment here about what happens. One can puzzle it together, but it took @DrMichaelPetter and me about 10 mins yesterday to do so.
| None -> a) | ||
| _ -> a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tracing output should still be produced if bitfield is off.
let a' = ID.meet a (ID.logxor c b) | ||
in let b' = ID.meet b (ID.logxor a c) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change to use standard indentation.
let ... = ... in
if PrecisionUtil.get_bitfield () then | ||
let a', b' = ID.meet a (ID.logor a c), ID.meet b (ID.logor b c) in | ||
let (cz, co) = ID.to_bitfield ikind c in | ||
let (az, ao) = ID.to_bitfield ikind a' in | ||
let (bz, bo) = ID.to_bitfield ikind b' in | ||
let cDef0 = Z.logand cz (Z.lognot co) in | ||
let aDef1 = Z.logand ao (Z.lognot az) in | ||
let bDef1 = Z.logand bo (Z.lognot bz) in | ||
let ao = Z.logand ao (Z.lognot (Z.logand bDef1 cDef0)) in | ||
let bo = Z.logand bo (Z.lognot (Z.logand aDef1 cDef0)) in | ||
ID.meet a' (ID.of_bitfield ikind (az, ao)), ID.meet b' (ID.of_bitfield ikind (bz, bo)) | ||
else a, b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
match x with None -> (Z.zero, Z.zero) | Some (c,m) -> | ||
if m = Z.zero then (Z.lognot c, c) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please indent as done in other places in Goblint.
else if is_power_of_two m then | ||
let mod_mask = m -: Z.one in | ||
let z = Z.lognot c in | ||
let o = Z.logor (Z.lognot mod_mask) c in | ||
(z,o) | ||
else (Z.lognot Z.zero, Z.lognot Z.zero) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does one need to consider the signedness here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure whether you mean the signedess of c and m or of the returned bitfield. But in both cases, we do not have to consider it explicitly. normalize
always returns positive values for c and m if m is unequal to zero. Additionally, a possible sign bit is set to top in the returned bitfield representation.
let refine_with_bitfield ik a (z,o) = | ||
if Z.lognot z = o then meet ik a (Some (o, Z.zero)) | ||
else a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the only case where we can benefit? Naively, one thinks we should be able to benefit along the reasoning of the to_bitfield
above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We benefit along the reasoning of the to_bitfield
in the refine_with_congruence
of the bitfield domain because we transform a value from the congruence into the bitfield domain. Unfortunately, I don't see how we can use similar tricks for the converse direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naively I would think that knowing that the last k
bits are must-zero would give rise to a
congruence of the type z mod 2^(k) = 0
, at least in the case where we also know that the values are positive (because of the ik, or because the MSB is not set)?
But maybe that's wrong?
let to_bitfield ik x = | ||
match x with | ||
`Definite c -> (Z.lognot c, c) | | ||
_ -> let one_mask = Z.lognot Z.zero | ||
in (one_mask, one_mask) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation
let refine_with_bitfield ik x (z,o) = | ||
if Z.lognot z = o then meet ik x (`Definite o) | ||
else x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you not need to consider the ik here?
let to_bitfield ik x = | ||
match x with | ||
Inc i when BISet.is_empty i -> (Z.zero, Z.zero) | | ||
Inc i when BISet.is_singleton i -> | ||
let o = BISet.choose i | ||
in (Z.lognot o, o) | | ||
Inc i -> BISet.fold (fun o (az, ao) -> (Z.logor (Z.lognot o) az, Z.logor o ao)) i (Z.zero, Z.zero) | | ||
_ -> let one_mask = Z.lognot Z.zero | ||
in (one_mask, one_mask) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this indentation it is near impossible to review!
@@ -1,4 +1,5 @@ | |||
open IntDomain0 | |||
open GoblintCil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are you using this? If it's only very few places, one may not want to open it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use it for the isSigned
function of ikinds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, since it's only used three times (and only in one function), I think i would personally prefer getting rid of the open
and qualifying those references with GoblintCil.isSigned
or using a local open in that function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
let rec from_list is acc = match is with | ||
[] -> acc | | ||
j::js -> from_list js (joinbf acc (Interval.to_bitfield ik (Some j))) | ||
in from_list x (Ints_t.zero, Ints_t.zero) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this not just a fold?
`top | ||
else | ||
if Ints_t.equal (Ints_t.logand (Ints_t.shift_right startv pos) Ints_t.one) Ints_t.one then | ||
`one | ||
else | ||
`zero |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where do these polymorphic variants now come from?
annotation_int_enabled := None; | ||
bitfield := None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please swap
@@ -380,7 +380,7 @@ struct | |||
let test_domain (module D: Lattice.S): unit = | |||
let module DP = DomainProperties.All (D) in | |||
Logs.debug "domain testing...: %s" (D.name ()); | |||
let errcode = QCheck_base_runner.run_tests DP.tests in | |||
let errcode = QCheck_base_runner.run_tests DP.tests ~verbose:true in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why verbose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious if you've actually used this and found anything with this. Domain tests for individual integer domains are part of unit tests where they are directly triggered. This attempts to test the full domain used by the configured analysis, but I suspect this doesn't have arbitrary
-s defined all the way down to the integer domains through all the functors. For that reason I've even considered removing this feature altogether, but if it has actually been useful then let me know.
@@ -0,0 +1,29 @@ | |||
// PARAM: --enable ana.int.bitfield |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general it seems very few of your pograms tests actually test the join of your domain. That would be something that probably should be added.
| (_, _, _, _, _, Some true) | ||
-> true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please stick with the normal indentation.
let rec concretize (z,o) = (* O(2^n) *) | ||
if is_const (z,o) then [o] | ||
else | ||
let bit = o &: Ints_t.one in | ||
let bf_bit = nth_bf_bit (z,o) 0 in | ||
concretize (z >>. 1, o >>: 1) |> | ||
if bf_bit = `Undetermined then | ||
List.concat_map (fun c -> [c <<: 1; (c <<: 1) |: Ints_t.one]) | ||
else if bf_bit = `Invalid then | ||
failwith "Should not have happened: Invalid bit during concretization of a bitfield." | ||
else | ||
List.map (fun c -> c <<: 1 |: bit) | ||
|
||
let concretize bf = List.map Ints_t.to_int (concretize bf) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'concretize' method is used inside the shift operations (shift_left, shift_right) as it returns the values which needs to be shifted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fully concretizing an abstract value is not something that should be done lightly, as you have noted yourself. If you do a shift and know nothing about the operand (which can happen), this operation alone will completely kill the chance of ever terminating, already from constructing this list of length 2^64.
Does your domain not have an efficient way of computing n
of zero bits in z xor x
(size 2^n)? In this case, you should only do something for small n (maybe up to 3) to stay tractable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see you actually bound the argument in all calls with the bitwidth, so it is not too problematic. Probably you should make a comment here as well then that the function is not a reasonable shift function for any arguments, but only for second arguments with small(!) concretizations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure that makes sense, I will add comments that make this more obvious.
let bot () = (BArith.zero_mask, BArith.zero_mask) | ||
|
||
let top_of ik = | ||
if isSigned ik then top () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think i understand how you handle ikinds
in general here.
- Do you norm the upper bits?
- If not do you not risk having values that are semantically equal but syntactically not?
- What happens for
ikind
s that are wider than theBArith.one_mask
?
Why can this not work for signed types? And does this not mean you find out nothing as soon as any computations happen in the program (even for unsigned types)?
C99 6.3.1.
If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer
promotions. All other types are unchanged by the integer promotions.
let o_mask = ref o in | ||
let z_mask = ref z in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why ref
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was not necessary to use references in this case, therefore the code was changed and the ref removed.
let range ik bf = (BArith.min ik bf, BArith.max ik bf) | ||
|
||
let maximal (z,o) = | ||
if (z < Ints_t.zero) <> (o < Ints_t.zero) then Some o |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We basically check whether the sign bit is clear, i.e., whether we only represent positive or negative numbers. Under such a condition, the maximal represented number is just the number with the maximal number of ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that works if the ikind
and the type of Ints_t
coincide. But what if you have, e.g., the ikind
of signed char
? There you would need to check the 7th bit right?
I wasn't there for the first couple of meetings, so maybe you'll need to enlighten us tomorrow how you deal with different ikind
s in your domain in general 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! I left some first comments in some places, but have not worked through the entire thing.
bug fix in to_bitfield()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the PR! I left some comments below.
let to_bitfield ik z = | ||
match z with None -> (Ints_t.lognot Ints_t.zero, Ints_t.lognot Ints_t.zero) | Some (x,y) -> | ||
let (min_ik, max_ik) = Size.range ik in | ||
let startv = Ints_t.max x (Ints_t.of_bigint min_ik) in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make this function easier to read?
A comment about what is done here overall may help. Possibly one may also find a more telling name for the helper function analyze_bits
?
let isNegative = Ints_t.logand signBit o <> Ints_t.zero in | ||
if isSigned ik && isNegative then Ints_t.logor signMask (Ints_t.lognot z) | ||
else Ints_t.lognot z | ||
in let max ik (z,o) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation.
let signBit = Ints_t.shift_left Ints_t.one ((Size.bit ik) - 1) in | ||
let signMask = Ints_t.lognot (Ints_t.of_bigint (snd (Size.range ik))) in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
signBit
and signMask
only seem to depend on ik
. Can one extract them from min
and max
, so they don't have to be defined twice?
assert_shift_right ~rev_cond:true ik (`I [max_of ik]) top top; (* the sign bit shouldn't be set with right shifts if its unset *) | ||
|
||
assert_shift_right ik (`I [2]) (`I [-1]) top; (* Negative shifts are undefined behavior *) | ||
(*assert_shift_right ik (`I [min_of ik]) top top;*) (*TODO implementation-defined sign-bit handling *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this todo about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test case could be removed if you agree with our design choice to handle right shifts as arithmetic ones.
Given the C standard's definition of right shifts on signed integers, it might be better to leave the upper bits undetermined. In that case this test case should pass.
| true, true -> `Undetermined | ||
| false, false -> `Invalid | ||
| true, false -> `Zero | ||
| false, true -> `One |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a specific reason you are using polymorphic variants? If not, it would be preferable to define a variant type. See also here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, they were naively chosen to be used for readability purposes here but not necessary in any way. This part will be removed.
else None | ||
|
||
let minimal (z,o) = | ||
if (z < Ints_t.zero) <> (o < Ints_t.zero) then Some (!:z) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the polymorphic compare <
here probably works, but the compare on Ints_t
is probably still the better choice?
(* (M.warn ~category:M.Category.Integer.overflow "Bitfield: Value was outside of range, indicating overflow, but 'sem.int.signed_overflow' is 'assume_none' -> Returned Top"; *) | ||
(* (bot (), overflow_info)) *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented out code should be removed in the final version.
|
||
(* Conversions *) | ||
|
||
let of_interval ?(suppress_ovwarn=false) ik (x,y) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this function share code with the to_bitfield
function in the interval domain? If so, could this be deduplicated?
if Ints_t.compare bigger_number endv <= 0 then | ||
`top | ||
else | ||
if Ints_t.equal (Ints_t.logand (Ints_t.shift_right startv pos) Ints_t.one) Ints_t.one then | ||
`one | ||
else | ||
`zero |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, one could also define a variant type, instead of using a polymorphic variant type.
(* Unit Tests *) | ||
|
||
let arbitrary ik = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
arbitrary
is used for unit tests, indeed; still the comment may be a bit misleading, since there are no unit tests following the comment.
This pull request enhances the IntDomain by introducing a new BitfieldDomain capable of tracking bitwise operations.
Additionally, it adds all newly possible refinement directions to improve precision in the IntDomain.
This implementation is part of the
Automated Bug Hunting and Beyond
practical course at TUM.