-
Notifications
You must be signed in to change notification settings - Fork 77
Dynamic check
Checked C's _Dynamic_check
operator is implemented within the compiler,
because we need to do static analysis on its argument to check it is a
non-modifying expression (NME).
To avoid having to extend the lexer/parser, we have implemented it as a Clang Builtin, which still gives us all the later hooks we need.
The check for non-modifying expressions is yet to be implemented.
For each dynamic check, we need to check a condition, and then, if the check fails, stop the program. If the check succeeds, the program will continue.
We insert a new basic block for each check. This block contains
only a llvm.trap
intrinsic call, which LLVM's code generation will
turn into either abort()
, or an instruction that does the same.
We try to insert these basic blocks at the end of the function so that
the generated code is easier to understand. There is no sharing of these
blocks so that dynamic check failures are easier to debug.
We also have to start a new basic block for when the check passes, which contains all the code after the check is finished. This is emitted directly after the conditional branch in the check.
An example function with two dynamic checks is below.
In LLVM IR, the things to look for are br
for branch, icmp
for
integer comparison, and call
for function calls.
define void @f1(i32 %i) #0 {
entry:
%i.addr = alloca i32, align 4
store i32 %i, i32* %i.addr, align 4
%0 = load i32, i32* %i.addr, align 4
%cmp = icmp ne i32 %0, 3
br i1 %cmp, label %_Dynamic_check_succeeded0, label %_Dynamic_check_failed1
_Dynamic_check_succeeded0: ; preds = %entry
%1 = load i32, i32* %i.addr, align 4
%cmp1 = icmp slt i32 %1, 50
br i1 %cmp1, label %_Dynamic_check_succeeded3, label %_Dynamic_check_failed2
_Dynamic_check_succeeded3: ; preds = %_Dynamic_check_succeeded0
ret void
_Dynamic_check_failed1: ; preds = %entry
call void @llvm.trap() #1
unreachable
_Dynamic_check_failed2: ; preds = %_Dynamic_check_succeeded0
call void @llvm.trap() #1
unreachable
}
If Clang/LLVM can evaluate the argument to a constant, one of two things will happen, depending on the value of the constant:
- If the constant is true, we elide the check, as we know it will always pass.
- If the constant is false, we keep the dynamic check, and we emit a warning
to tell the developer their check will always fail. The reason this is not
a warning is because the
dynamic_check
may have been generated by code they have no control over, and therefore an error will get in their way in a way a warning won't. If they choose, they can turn all compiler warnings into errors, but that's under their control.
This is an optimization; soundness is preserved if this transformation is never performed.