Skip to content

Commit 63520dc

Browse files
Bitwise operations (#579)
This PR implements implements bitwise operators and fixing a bug uncovered doing so. ## Bitwise Operators Bitwise operators `&` "And", `|` "Or", `^` "Xor": - `&`, `|`, `^` on integers - `&`, `|`, `^` on booleans - `&`, `|`, `^` on borrows (Not completed, stuck on `GlobalAllocs` / promoteds) both LHS and RHS of the operation must have the same type. Shifts `<<` and `>>` are also implemented, although shifts are broken down into these commands: - `Shl` shift left with wrapping on overflow - `Shr` arithmetic shift right with wrapping on overflow - `ShlUnchecked` shift left with UB on overflow - `ShrUnchecked` arithmetic shift right with UB on overflow where overflow is defined as the right hand arg (shift amount) being greater than the number of bits in the left hand arg. Concrete proofs are added for the above, borrows are currently failing. ## Bug Found and Fixed This PR also uncovered a bug we had in how we were handling `Terminator` `SwitchInt`. This value is _always_ interpreted as an unsigned value (even if the branching is on a negative value). We were considering the magnitude of the value, but needed to interpret the bits as unsigned. --------- Co-authored-by: devops <[email protected]>
1 parent cacac52 commit 63520dc

File tree

13 files changed

+209
-15
lines changed

13 files changed

+209
-15
lines changed

kmir/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "kmir"
7-
version = "0.3.161"
7+
version = "0.3.162"
88
description = ""
99
requires-python = "~=3.10"
1010
dependencies = [

kmir/src/kmir/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from typing import Final
22

3-
VERSION: Final = '0.3.161'
3+
VERSION: Final = '0.3.162'

kmir/src/kmir/kdist/mir-semantics/kmir.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,10 @@ block after the call returns.
321321
```
322322

323323
A `SwitchInt` terminator selects one of the blocks given as _targets_,
324-
depending on the value of a _discriminant_.
324+
depending on the value of a _discriminant_. If the discriminant is an
325+
an integer, it is always interpretted as the _unsigned_ value (even if
326+
negative). E.g. if branching is occuring on `-127_i8`, the discriminant
327+
will be `129`.
325328

326329
```k
327330
syntax KItem ::= #selectBlock ( SwitchTargets , Evaluation ) [strict(2)]
@@ -331,9 +334,9 @@ depending on the value of a _discriminant_.
331334
#selectBlock(TARGETS, DISCR)
332335
</k>
333336
334-
rule <k> #selectBlock(TARGETS, typedValue(Integer(I, _, _), _, _))
337+
rule <k> #selectBlock(TARGETS, typedValue(Integer(I, WIDTH, _), _, _))
335338
=>
336-
#execBlockIdx(#selectBlock(I, TARGETS))
339+
#execBlockIdx(#selectBlock(bitRangeInt(I, 0, WIDTH), TARGETS))
337340
...
338341
</k>
339342

kmir/src/kmir/kdist/mir-semantics/rt/data.md

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,13 +1155,106 @@ The `unOpNot` operation works on boolean and integral values, with the usual sem
11551155

11561156
#### Bit-oriented operations
11571157

1158-
`binOpBitXor`
1159-
`binOpBitAnd`
1160-
`binOpBitOr`
1161-
`binOpShl`
1162-
`binOpShlUnchecked`
1163-
`binOpShr`
1164-
`binOpShrUnchecked`
1158+
Bitwise operations `binOpBitXor`, `binOpBitAnd`, and `binOpBitOr` are valid between integers, booleans, and borrows; but only if the type of left and right arguments is the same.
1159+
1160+
TODO: Borrows. Stuck on global allocs / promoteds
1161+
1162+
Shifts are valid on integers if the right argument (the shift amount) is strictly less than the width of the left argument. Right shifts on negative numbers are arithmetic shifts and preserve the sign. There are two variants (checked and unchecked), checked will wrap on overflow and not trigger UB, unchecked will trigger UB on overflow. The UB case currently gets stuck.
1163+
1164+
```k
1165+
syntax Bool ::= isBitwise ( BinOp ) [function, total]
1166+
// --------------------------------------------------
1167+
rule isBitwise(binOpBitXor) => true
1168+
rule isBitwise(binOpBitAnd) => true
1169+
rule isBitwise(binOpBitOr) => true
1170+
rule isBitwise(_) => false [owise]
1171+
rule onInt(binOpBitXor, X, Y) => X xorInt Y
1172+
rule onInt(binOpBitAnd, X, Y) => X &Int Y
1173+
rule onInt(binOpBitOr, X, Y) => X |Int Y
1174+
1175+
syntax Bool ::= onBool( BinOp, Bool, Bool ) [function]
1176+
// ---------------------------------------------------
1177+
rule onBool(binOpBitXor, X, Y) => X xorBool Y
1178+
rule onBool(binOpBitAnd, X, Y) => X andBool Y
1179+
rule onBool(binOpBitOr, X, Y) => X orBool Y
1180+
1181+
syntax Bool ::= isShift ( BinOp ) [function, total]
1182+
// ------------------------------------------------
1183+
rule isShift(binOpShl) => true
1184+
rule isShift(binOpShlUnchecked) => true
1185+
rule isShift(binOpShr) => true
1186+
rule isShift(binOpShrUnchecked) => true
1187+
rule isShift(_) => false [owise]
1188+
1189+
syntax Bool ::= isUncheckedShift ( BinOp ) [function, total]
1190+
// ------------------------------------------------
1191+
rule isUncheckedShift(binOpShlUnchecked) => true
1192+
rule isUncheckedShift(binOpShrUnchecked) => true
1193+
rule isUncheckedShift(_) => false [owise]
1194+
1195+
syntax Int ::= onShift( BinOp, Int, Int, Int ) [function]
1196+
// ---------------------------------------------------
1197+
rule onShift(binOpShl, X, Y, WIDTH) => (X <<Int Y) modInt (1 <<Int WIDTH)
1198+
rule onShift(binOpShr, X, Y, WIDTH) => (X >>Int Y) modInt (1 <<Int WIDTH)
1199+
rule onShift(binOpShlUnchecked, X, Y, WIDTH) => (X <<Int Y) modInt (1 <<Int WIDTH)
1200+
rule onShift(binOpShrUnchecked, X, Y, WIDTH) => (X >>Int Y) modInt (1 <<Int WIDTH)
1201+
1202+
rule #compute(
1203+
BOP,
1204+
typedValue(Integer(ARG1, WIDTH, SIGNED), TY, _),
1205+
typedValue(Integer(ARG2, WIDTH, SIGNED), TY, _),
1206+
false) // unchecked
1207+
=>
1208+
typedValue(
1209+
Integer(onInt(BOP, ARG1, ARG2), WIDTH, SIGNED),
1210+
TY,
1211+
mutabilityNot
1212+
)
1213+
requires isBitwise(BOP)
1214+
[preserves-definedness]
1215+
1216+
rule #compute(
1217+
BOP,
1218+
typedValue(BoolVal(ARG1), TY, _),
1219+
typedValue(BoolVal(ARG2), TY, _),
1220+
false) // unchecked
1221+
=>
1222+
typedValue(
1223+
BoolVal(onBool(BOP, ARG1, ARG2)),
1224+
TY,
1225+
mutabilityNot
1226+
)
1227+
requires isBitwise(BOP)
1228+
[preserves-definedness]
1229+
1230+
rule #compute(
1231+
BOP,
1232+
typedValue(Integer(ARG1, WIDTH, false), TY, _),
1233+
typedValue(Integer(ARG2, _, _), _, _),
1234+
false) // unchecked
1235+
=>
1236+
typedValue(
1237+
Integer(truncate(onShift(BOP, ARG1, ARG2, WIDTH), WIDTH, Unsigned), WIDTH, false),
1238+
TY,
1239+
mutabilityNot
1240+
)
1241+
requires isShift(BOP) andBool ((notBool isUncheckedShift(BOP)) orBool ARG2 <Int WIDTH)
1242+
[preserves-definedness]
1243+
1244+
rule #compute(
1245+
BOP,
1246+
typedValue(Integer(ARG1, WIDTH, true), TY, _),
1247+
typedValue(Integer(ARG2, _, _), _, _),
1248+
false) // unchecked
1249+
=>
1250+
typedValue(
1251+
Integer(truncate(onShift(BOP, ARG1, ARG2, WIDTH), WIDTH, Signed), WIDTH, true),
1252+
TY,
1253+
mutabilityNot
1254+
)
1255+
requires isShift(BOP) andBool ((notBool isUncheckedShift(BOP)) orBool ARG2 <Int WIDTH)
1256+
[preserves-definedness]
1257+
```
11651258

11661259

11671260
#### Nullary operations for activating certain checks
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
fn main() {
2+
// Integers
3+
assert!(3_u128 & 7_u128 == 3_u128);
4+
assert!(3_u128 | 7_u128 == 7_u128);
5+
assert!(3_u128 ^ 7_u128 == 4_u128);
6+
7+
// Booleans
8+
assert!(false | false == false);
9+
assert!(true | false == true);
10+
assert!(true & true == true);
11+
assert!(true & false == false);
12+
assert!(true ^ true == false);
13+
assert!(true ^ false == true);
14+
15+
// Borrows
16+
assert!(&3 & &7 == 3);
17+
assert!(&3 | &7 == 7);
18+
assert!(&3 ^ &7 == 4);
19+
assert!(&false & &false == false);
20+
assert!(&true | &true == true);
21+
assert!(&false ^ &false == false);
22+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
fn main() {
2+
// Shl basic
3+
assert!(1_u8 << 3_u8 == 8);
4+
assert!(1_u8 << 3_i8 == 8); // Negative on RHS if in 0..size
5+
assert!(1_i8 << 3_u8 == 8);
6+
assert!(1_i8 << 3_i8 == 8);
7+
assert!(1_i8 << 7_u8 == -128_i8);
8+
9+
// Shl Overflow
10+
assert!(255_u8 << 1_u8 == 254);
11+
assert!(-127_i8 << 1_u8 == 2);
12+
// assert!(-128_i8 << -1_i8 == 2); // Shift must be in 0..size
13+
// assert!(0_u8 << 8_u8 == 0); // Shift must be in 0..size
14+
15+
// Shr basic
16+
assert!(24_u8 >> 3_u8 == 3);
17+
assert!(24_u8 >> 3_i8 == 3); // Negative on RHS if in 0..size
18+
assert!(24_i8 >> 3_u8 == 3);
19+
assert!(24_i8 >> 3_i8 == 3);
20+
21+
// Shr Overflow
22+
assert!(255_u8 >> 1_u8 == 127);
23+
assert!(-127_i8 >> 1_u8 == -64); // Arithmetic shift sign extends
24+
// assert!(-128_i8 >> -1_i8 == 2); // Shift must be in 0..size
25+
// assert!(0_u8 >> 8_u8 == 0); // Shift must be in 0..size
26+
27+
// ShlUnchecked basic
28+
assert!(1_u8.wrapping_shl(3_u32) == 8_u8); // RHS must be u32
29+
30+
// ShlUnchecked Overflow
31+
assert!(255_u8.wrapping_shl(1_u32) == 254_u8);
32+
assert!(128_u8.wrapping_shl(1_u32) == 0_u8);
33+
assert!((-128_i8).wrapping_shl(3_u32) == 0_i8);
34+
assert!((-127_i8).wrapping_shl(3_u32) == 8_i8);
35+
36+
// ShrUnchecked basic
37+
assert!(32_u8.wrapping_shr(2_u32) == 8_u8); // RHS must be u32
38+
39+
// ShlUnchecked Overflow
40+
assert!(255_u8.wrapping_shr(1_u32) == 127_u8);
41+
assert!(128_u8.wrapping_shr(1_u32) == 64_u8);
42+
assert!((-128_i8).wrapping_shr(3_u32) == -16_i8);
43+
assert!((-127_i8).wrapping_shr(1_u32) == -64_i8);
44+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
assert!((1_i8 << 7) + 1 == -127_i8);
3+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
fn main() {
2+
assert!(1_i8 << 7_u8 == -128);
3+
assert!(1_i16 << 15_u8 == -32768);
4+
assert!(1_i32 << 31_u8 == -2147483648);
5+
assert!(1_i64 << 63_u8 == -9223372036854775808);
6+
assert!(1_i128 << 127_u8 == -170141183460469231731687303715884105728);
7+
8+
assert!(1_i8 << 7_u8 == i8::MIN);
9+
assert!(1_i16 << 15_u8 == i16::MIN);
10+
assert!(1_i32 << 31_u8 == i32::MIN);
11+
assert!(1_i64 << 63_u8 == i64::MIN);
12+
assert!(1_i128 << 127_u8 == i128::MIN);
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
┌─ 1 (root, init)
3+
│ #init ( symbol ( "bitwise_not_shift_fail" ) globalAllocEntry ( 8 , Memory ( allo
4+
│ function: main
5+
│ span: 108
6+
7+
│ (254 steps)
8+
└─ 3 (stuck, leaf)
9+
#readProjection ( typedValue ( Any , ty ( 25 ) , mutabilityNot ) , projectionEle
10+
span: 63
11+
12+
13+
┌─ 2 (root, leaf, target, terminal)
14+
│ #EndProgram
15+
16+

kmir/src/tests/integration/test_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ def test_prove(spec: Path, tmp_path: Path, kmir: KMIR) -> None:
434434
'interior-mut2-fail',
435435
'interior-mut3-fail',
436436
'assert_eq_exp-fail',
437-
'bitwise-fail',
437+
'bitwise-not-shift-fail',
438438
]
439439

440440

kmir/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.161
1+
0.3.162

0 commit comments

Comments
 (0)