Skip to content

Commit c81b40e

Browse files
jbertholdrv-auditordkcumming
authored
Remove error rules for reading and writing locals (#550)
Remove error rules for reading and writing locals (type mismatch, moved and uninitialised values) which should not occur in valid MIR. These cases will now get stuck instead of producing an explained error. We still match on the preconditions for valid list indexing and could remove that, too, but we still _have_ to index into the locals so there is no big gain not doing that. Remove errors related to type mismatch and operand mismatch in binary and unary operation rules. Likewise, these are invalid MIR code and the operation will get stuck now. Also removed: the case of unimplemented operations. NB dynamic errors (overflow and division by zero) are still signalled as specific errors. --------- Co-authored-by: devops <devops@runtimeverification.com> Co-authored-by: dkcumming <daniel.cumming@runtimeverification.com>
1 parent 0935d9f commit c81b40e

7 files changed

Lines changed: 82 additions & 157 deletions

File tree

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.134"
7+
version = "0.3.135"
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.134'
3+
VERSION: Final = '0.3.135'

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

Lines changed: 44 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,10 @@ Other uses of heating and cooling are to _read_ local variables as operands. Thi
4343
### Errors Related to Accessing Local Variables
4444

4545
Access to a `TypedLocal` (whether reading or writing) may fail for a number of reasons.
46-
It is an error to use a `Moved` local in any way, or to read an uninitialised `NewLocal`.
47-
Also, locals are accessed via their index in list `<locals>` in a stack frame, which may be out of bounds.
48-
Types are also checked, using the `Ty` (an opaque number assigned by the Stable MIR extraction).
49-
50-
```k
51-
syntax LocalAccessError ::= InvalidLocal ( Local )
52-
| TypeMismatch( Local, MaybeTy, TypedValue )
53-
| LocalMoved( Local )
54-
| LocalNotMutable ( Local )
55-
| LocalUninitialised( Local )
56-
```
46+
It is an error to read a `Moved` local or an uninitialised `NewLocal`.
47+
Also, locals are accessed via their index in list `<locals>` in a stack frame, which may be out of bounds
48+
(but the compiler should guarantee that all local indexes are valid).
49+
Types (`Ty`, an opaque number assigned by the Stable MIR extraction) are not checked, the local's type is used.
5750

5851
### Reading Operands (Local Variables and Constants)
5952

@@ -96,28 +89,7 @@ local value cannot be read, though, and the value should be initialised.
9689
andBool isTypedValue(LOCALS[I])
9790
[preserves-definedness] // valid list indexing checked
9891
99-
rule <k> operandCopy(place(local(I) #as LOCAL, _))
100-
=>
101-
LocalMoved(LOCAL)
102-
...
103-
</k>
104-
<locals> LOCALS </locals>
105-
requires 0 <=Int I
106-
andBool I <Int size(LOCALS)
107-
andBool isMovedLocal(LOCALS[I])
108-
[preserves-definedness] // valid list indexing checked
109-
110-
rule <k> operandCopy(place(local(I), _))
111-
=>
112-
LocalUninitialised(local(I))
113-
...
114-
</k>
115-
<locals> LOCALS </locals>
116-
requires I <Int size(LOCALS)
117-
andBool 0 <=Int I
118-
andBool isNewLocal(LOCALS[I])
119-
[preserves-definedness] // valid list indexing checked
120-
// TODO how about zero-sized types
92+
// error cases (NewLocal, Moved) get stuck
12193
```
12294

12395
Reading an `Operand` using `operandMove` has to invalidate the respective local, to prevent any
@@ -135,27 +107,7 @@ further access. Apart from that, the same caveats apply as for operands that are
135107
andBool isTypedValue(LOCALS[I])
136108
[preserves-definedness] // valid list indexing checked
137109
138-
rule <k> operandMove(place(local(I) #as LOCAL, _))
139-
=>
140-
LocalMoved(LOCAL)
141-
...
142-
</k>
143-
<locals> LOCALS </locals>
144-
requires 0 <=Int I
145-
andBool I <Int size(LOCALS)
146-
andBool isMovedLocal(LOCALS[I])
147-
[preserves-definedness] // valid list indexing checked
148-
149-
rule <k> operandMove(place(local(I), _))
150-
=>
151-
LocalUninitialised(local(I))
152-
...
153-
</k>
154-
<locals> LOCALS </locals>
155-
requires 0 <=Int I
156-
andBool I <Int size(LOCALS)
157-
andBool isNewLocal(LOCALS[I])
158-
[preserves-definedness] // valid list indexing checked
110+
// error cases (NewLocal, Moved) get stuck
159111
```
160112

161113
#### Reading places with projections
@@ -298,7 +250,7 @@ In the simplest case, the reference refers to a local in the same stack frame (h
298250
andBool isTypedValue(LOCALS[I])
299251
[preserves-definedness] // valid list indexing checked
300252
301-
// TODO case of MovedLocal and NewLocal
253+
// Moved and NewLocal get stuck
302254
303255
// why do we not have this automatically for user-defined lists?
304256
syntax ProjectionElems ::= appendP ( ProjectionElems , ProjectionElems ) [function, total]
@@ -392,51 +344,15 @@ The `#setLocalValue` operation writes a `TypedLocal` value to a given `Place` wi
392344
```k
393345
syntax KItem ::= #setLocalValue( Place, Evaluation ) [strict(2)]
394346
395-
// error cases first
396-
rule <k> #setLocalValue( place(local(I) #as LOCAL, _), _) => InvalidLocal(LOCAL) ... </k>
397-
<locals> LOCALS </locals>
398-
requires size(LOCALS) <=Int I orBool I <Int 0
399-
400-
rule <k> #setLocalValue( place(local(I) #as LOCAL, .ProjectionElems), typedValue(_, TY, _) #as VAL)
401-
=>
402-
TypeMismatch(LOCAL, tyOfLocal({LOCALS[I]}:>TypedLocal), VAL)
403-
...
404-
</k>
405-
<locals> LOCALS </locals>
406-
requires 0 <=Int I
407-
andBool I <Int size(LOCALS)
408-
andBool isTypedLocal(LOCALS[I])
409-
andBool TY =/=K TyUnknown
410-
andBool tyOfLocal({LOCALS[I]}:>TypedLocal) =/=K TY
411-
[preserves-definedness] // list index checked before lookup
347+
// error cases (not checked, just matched below to prevent them)
412348
413-
// setting a local which was Moved is an error
414-
rule <k> #setLocalValue( place(local(I), .ProjectionElems), _)
415-
=>
416-
LocalMoved(local(I))
417-
...
418-
</k>
419-
<locals> LOCALS </locals>
420-
requires 0 <=Int I
421-
andBool I <Int size(LOCALS)
422-
andBool isMovedLocal(LOCALS[I])
423-
[priority(60), preserves-definedness] // list index checked before lookup
349+
// no type check, the local's type is used.
424350
425351
// setting a non-mutable local that is initialised is an error
426-
rule <k> #setLocalValue( place(local(I) #as LOCAL, .ProjectionElems), _)
427-
=>
428-
LocalNotMutable(LOCAL)
429-
...
430-
</k>
431-
<locals> LOCALS </locals>
432-
requires I <Int size(LOCALS)
433-
andBool 0 <=Int I
434-
andBool isTypedValue(LOCALS[I])
435-
andBool mutabilityOf({LOCALS[I]}:>TypedLocal) ==K mutabilityNot
436352
437353
// if all is well, write the value
438354
// mutable local
439-
rule <k> #setLocalValue(place(local(I), .ProjectionElems), typedValue(VAL:Value, TY, _ ))
355+
rule <k> #setLocalValue(place(local(I), .ProjectionElems), typedValue(VAL:Value, _, _ ))
440356
=>
441357
.K
442358
...
@@ -448,22 +364,34 @@ The `#setLocalValue` operation writes a `TypedLocal` value to a given `Place` wi
448364
andBool I <Int size(LOCALS)
449365
andBool isTypedValue(LOCALS[I])
450366
andBool mutabilityOf({LOCALS[I]}:>TypedLocal) ==K mutabilityMut
451-
andBool (tyOfLocal({LOCALS[I]}:>TypedLocal) ==K TY orBool TY ==K TyUnknown) // matching or unknown type
452367
[preserves-definedness] // valid list indexing checked
453368
454-
// special case for non-mutable uninitialised values: mutable once
455-
rule <k> #setLocalValue(place(local(I), .ProjectionElems), typedValue(VAL:Value, TY, _ ))
369+
// non-mutable uninitialised values are mutable once
370+
rule <k> #setLocalValue(place(local(I), .ProjectionElems), typedValue(VAL:Value, _, _ ))
456371
=>
457372
.K
458373
...
459374
</k>
460-
<locals>
461-
LOCALS => LOCALS[I <- typedValue(VAL, tyOfLocal({LOCALS[I]}:>TypedLocal), mutabilityOf({LOCALS[I]}:>TypedLocal))]
375+
<locals>
376+
LOCALS => LOCALS[I <- typedValue(VAL, tyOfLocal({LOCALS[I]}:>TypedLocal), mutabilityOf({LOCALS[I]}:>TypedLocal))]
462377
</locals>
463378
requires 0 <=Int I
464379
andBool I <Int size(LOCALS)
465380
andBool isNewLocal(LOCALS[I])
466-
andBool (tyOfLocal({LOCALS[I]}:>TypedLocal) ==K TY orBool TY ==K TyUnknown) // matching or unknown type
381+
[preserves-definedness] // valid list indexing checked
382+
383+
// reusing a local which was Moved is allowed
384+
rule <k> #setLocalValue(place(local(I), .ProjectionElems), typedValue(VAL:Value, _, _ ))
385+
=>
386+
.K
387+
...
388+
</k>
389+
<locals>
390+
LOCALS => LOCALS[I <- typedValue(VAL, tyOfLocal({LOCALS[I]}:>TypedLocal), mutabilityOf({LOCALS[I]}:>TypedLocal))]
391+
</locals>
392+
requires 0 <=Int I
393+
andBool I <Int size(LOCALS)
394+
andBool isMovedLocal(LOCALS[I])
467395
[preserves-definedness] // valid list indexing checked
468396
```
469397

@@ -711,7 +639,7 @@ The `RValue::Repeat` creates and array of (statically) fixed length by repeating
711639
rule <k> rvalueLen(PLACE) => #lengthU64(operandCopy(PLACE)) ... </k>
712640
713641
rule <k> #lengthU64(typedValue(Range(LIST), _, _))
714-
=>
642+
=>
715643
typedValue(Integer(size(LIST), 64, false), TyUnknown, mutabilityNot) // returns usize
716644
...
717645
</k>
@@ -724,7 +652,7 @@ Likewise built into the language are aggregates (tuples and `struct`s) and varia
724652
Besides their list of arguments, `enum`s also carry a `VariantIdx` indicating which variant was used. For tuples and `struct`s, this index is always 0.
725653

726654
Tuples, `struct`s, and `enum`s are built as `Aggregate` values with a list of argument values.
727-
For `enums`, the `VariantIdx` is set, and for `struct`s and `enum`s, the type ID (`Ty`) is retrieved from a special mapping of `AdtDef` to `Ty`.
655+
For `enums`, the `VariantIdx` is set, and for `struct`s and `enum`s, the type ID (`Ty`) is retrieved from a special mapping of `AdtDef` to `Ty`.
728656

729657
```k
730658
rule <k> rvalueAggregate(KIND, ARGS) => #readOperands(ARGS) ~> #mkAggregate(KIND) ... </k>
@@ -852,7 +780,7 @@ rewriting `typedLocal(...) ~> #cast(...) ~> REST` to `typedLocal(...) ~> REST`.
852780
853781
```
854782

855-
### Integer Type Casts
783+
### Integer Type Casts
856784

857785
Casts between signed and unsigned integral numbers of different width exist, with a
858786
truncating semantics. These casts can only operate on the `Integer` variant of the `Value` type, adjusting
@@ -919,7 +847,7 @@ The `Value` sort above operates at a higher level than the bytes representation
919847
920848
// Integer: handled in separate module for numeric operations
921849
rule #decodeConstant(constantKindAllocated(allocation(BYTES, _, _, _)), TYPEINFO)
922-
=>
850+
=>
923851
#decodeInteger(BYTES, #intTypeOf(TYPEINFO))
924852
requires #isIntType(TYPEINFO)
925853
andBool lengthBytes(BYTES) ==K #bitWidth(#intTypeOf(TYPEINFO)) /Int 8
@@ -972,29 +900,15 @@ There are also a few _unary_ operations (`UnOpNot`, `UnOpNeg`, `UnOpPtrMetadata`
972900
```k
973901
syntax MIRError ::= OperationError
974902
975-
syntax OperationError ::= TypeMismatch ( BinOp, Ty, Ty )
976-
| OperandMismatch ( BinOp, Value, Value )
977-
| OperandError( BinOp, TypedLocal, TypedLocal)
978-
| OperandMismatch ( UnOp, Value )
979-
| OperandError( UnOp, TypedLocal)
980-
// errors above are compiler bugs or invalid MIR
981-
| OpNotimplemented ( BinOp, TypedValue, TypedValue)
982-
// errors below are program errors
983-
| "DivisionByZero"
903+
// (dynamic) program errors causing undefined behaviour
904+
syntax OperationError ::= "DivisionByZero"
984905
| "Overflow_U_B"
985906
986-
// catch Moved or uninitialised operands
987-
rule #compute(OP, ARG1, ARG2, _FLAG) => OperandError(OP, ARG1, ARG2)
988-
requires notBool (isTypedValue(ARG1) andBool isTypedValue(ARG2))
907+
// errors caused by invalid MIR code get stuck
989908
990-
rule #applyUnOp(OP, ARG) => OperandError(OP, ARG)
991-
requires notBool isTypedValue(ARG)
909+
// Moved or uninitialised operands
992910
993-
// catch-all rule to make `#compute` total
994-
rule #compute(OP, ARG1, ARG2, _FLAG) => OpNotimplemented(OP, ARG1, ARG2)
995-
requires isTypedValue(ARG1)
996-
andBool isTypedValue(ARG2)
997-
[owise]
911+
// specific errors for the particular operation types (argument or type mismatches)
998912
```
999913

1000914
#### Arithmetic
@@ -1040,6 +954,10 @@ The arithmetic operations require operands of the same numeric type.
1040954
requires Y =/=Int 0
1041955
// operation undefined otherwise
1042956
957+
// error cases for isArithmetic(BOP):
958+
// * arguments must have the same type (TY match)
959+
// * arguments must be Numbers
960+
1043961
// Checked operations return a pair of the truncated value and an overflow flag
1044962
// signed numbers: must check for wrap-around (operation specific)
1045963
rule #compute(
@@ -1167,22 +1085,6 @@ The arithmetic operations require operands of the same numeric type.
11671085
requires DIVISOR ==Int -1
11681086
andBool DIVIDEND ==Int 0 -Int (1 <<Int (WIDTH -Int 1)) // == minInt
11691087
[priority(40)]
1170-
1171-
// error cases:
1172-
// non-integer arguments
1173-
rule #compute(BOP, typedValue(ARG1, TY, _), typedValue(ARG2, TY, _), _)
1174-
=>
1175-
OperandMismatch(BOP, ARG1, ARG2)
1176-
requires isArithmetic(BOP)
1177-
[owise]
1178-
1179-
// different argument types
1180-
rule #compute(BOP, typedValue(_, TY1, _), typedValue(_, TY2, _), _)
1181-
=>
1182-
TypeMismatch(BOP, TY1, TY2)
1183-
requires TY1 =/=K TY2
1184-
andBool isArithmetic(BOP)
1185-
[owise]
11861088
```
11871089

11881090
#### Comparison operations
@@ -1218,9 +1120,9 @@ All operations except `binOpCmp` return a `BoolVal`. The argument types must be
12181120
rule cmpOpBool(binOpGe, X, Y) => cmpOpBool(binOpLe, Y, X)
12191121
rule cmpOpBool(binOpGt, X, Y) => cmpOpBool(binOpLt, Y, X)
12201122
1221-
rule #compute(OP, typedValue(_, TY, _), typedValue(_, TY2, _), _) => TypeMismatch(OP, TY, TY2)
1222-
requires isComparison(OP)
1223-
andBool TY =/=K TY2
1123+
// error cases for isComparison and binOpCmp:
1124+
// * arguments must have the same type
1125+
// * arguments must be numbers or Bool
12241126
12251127
rule #compute(OP, typedValue(Integer(VAL1, WIDTH, SIGN), TY, _), typedValue(Integer(VAL2, WIDTH, SIGN), TY, _), _)
12261128
=>
@@ -1233,12 +1135,6 @@ All operations except `binOpCmp` return a `BoolVal`. The argument types must be
12331135
typedValue(BoolVal(cmpOpBool(OP, VAL1, VAL2)), TyUnknown, mutabilityNot)
12341136
requires isComparison(OP)
12351137
[preserves-definedness] // OP known to be a comparison
1236-
1237-
rule #compute(OP, typedValue(ARG1, TY, _), typedValue(ARG2, TY, _), _)
1238-
=>
1239-
OperandMismatch(OP, ARG1, ARG2)
1240-
requires isComparison(OP)
1241-
[owise]
12421138
```
12431139

12441140
The `binOpCmp` operation returns `-1`, `0`, or `+1` (the behaviour of Rust's `std::cmp::Ordering as i8`), indicating `LE`, `EQ`, or `GT`.
@@ -1300,11 +1196,6 @@ The `unOpNot` operation works on boolean and integral values, with the usual sem
13001196
</k>
13011197
```
13021198

1303-
```k
1304-
rule <k> #applyUnOp(OP, typedValue(VAL, _, _)) => OperandMismatch(OP, VAL) ... </k>
1305-
[owise]
1306-
```
1307-
13081199
#### Bit-oriented operations
13091200

13101201
`binOpBitXor`
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
fn property_test() {
2+
let mut x = 0;
3+
let y = 10;
4+
let mut s = 0;
5+
6+
while x < y {
7+
x += 1;
8+
s += x;
9+
}
10+
11+
assert!(s == 56);
12+
13+
}
14+
15+
fn main() {
16+
property_test();
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
fn property_test() {
2+
let mut x = 0;
3+
let y = 10;
4+
let mut s = 0;
5+
6+
while x < y {
7+
x += 1;
8+
s += x;
9+
}
10+
11+
assert!(s == 55);
12+
13+
}
14+
15+
fn main() {
16+
property_test();
17+
}

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.134
1+
0.3.135

0 commit comments

Comments
 (0)