Skip to content

Dynamic check

Anna Kornfeld Simpson edited this page Jul 17, 2018 · 1 revision

Dynamic Check Operator

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.

Semantic Analysis - NME

The check for non-modifying expressions is yet to be implemented.

Code Generation

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
}

Constant Evaluation

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.