Page 1
Bit Level Types for High Level Reasoning
Ranjit Jhala
UC San Diego
jhala@cs.ucsd.edu
Rupak Majumdar
UC Los Angeles
rupak@cs.ucla.edu
ABSTRACT
Bitwise operations are commonly used in lowlevel systems
code to access multiple data fields that have been packed
into a single word. Program analysis tools that reason about
such programs must model the semantics of bitwise opera
tions precisely in order to capture program control and data
flow through these operations. We present a type system for
subword data structures that explitictly tracks the flow of
bit values in the program and identifies consecutive sections
of bits as logical entities manipulated atomically by the pro
grammer. Our type inference algorithm tags each integer
value of the program with a bitvector type that identifies the
data layout at the subword level. These types are used in a
translation phase to remove bitwise operations from the pro
gram, thereby allowing verification engines to avoid the ex
pensive lowlevel reasoning required for analyzing bitvector
operations. We have used a software model checker to check
properties of translated versions of a Linux device driver
and a memory protection system. The resulting verifica
tion runs could prove many more properties than the naive
model checker that did not reason about bitvectors, and
could prove properties much faster than a model checker
that did reason about bitvectors. We have also applied our
bitvector type inference algorithm to generate program doc
umentation for a virtual memory subsystem of an OS kernel.
While we have applied the type system mainly for program
understanding and verification, bitvector types also have ap
plications to better variable ordering heuristics in boolean
model checking and memory optimizations in compilers for
embedded software.
Categories and Subject Descriptors: D.2.4 [Software
Engineering]: Software/Program Verification.
General Terms: Languages, Verification, Reliability.
Keywords: Bit vectors, type inference, model checking.
1. INTRODUCTION
Many programs manipulate data at subword levels where
multiple data fields are packed into a single word. For ex
Permission to make digital or hard copies of all or part of this work for
personal or classroom use is granted without fee provided that copies are
not made or distributed for profit or commercial advantage and that copies
bear this notice and the full citation on the first page. To copy otherwise, to
republish, to post on servers or to redistribute to lists, requires prior specific
permission and/or a fee.
SIGSOFT’06/FSE14, November 5–11, 2006, Portland, Oregon, USA.
Copyright 2006 ACM 1595934685/06/0011 ..$5.00
ample, in the virtual memory subsystem of an operating
system, a 32bit linear address can represent 20 bits of an
index into a page table, 10 more bits as an offset into a
page, and two permission bits. Similarly, in embedded ap
plications where the memory footprint must be small, dif
ferent fields must often be packed (sometimes automatically
by a compiler) into one word. These subword records are
manipulated by the programmer using bitlevel operations,
namely, masks and shifts. To reason about such programs
manually or automatically we require techniques that ac
curately capture the flow of information through bitvector
variables and the shifting and masking operations.
There are usually three ways of handling bitvectors in
program analysis. The first is to treat bitvector operations
as uninterpreted functions, ignoring the semantics of these
operations. The second is to use specialized decision pro
cedures for bitvectors [17, 2, 14]. The third is to reduce
all variables to (32 or 64) individual bits (“bitblasting”)
and use propositional reasoning [3, 23]. The first option,
while surprisingly common [1, 8, 5, 10], especially when
the tool builder does not anticipate the use of bitvectors,
produces imprecisions in the analysis that show up as false
positives.In our experience, the second approach of us
ing specialized decision procedures makes the analysis much
slower, just because specialized decision procedures are com
paratively slower than for example, welltuned procedures
for the theory of equality and arithmetic available in de
cision procedures [6, 20]. The third option is attractive,
due to the efficiency of SAT solvers and BDD engines, and
the accuracy with which the machine semantics is reflected
[3, 23]. While this option has been successfully applied in
“bounded” analyses for finding bugs, the bitblasting loses
the highlevel relationships between the variables that are
critical for statically computing invariants, thereby making
the option unsuitable for safety verification.
In this paper, we provide a fourth, typebased, option. We
define a type system at the bit level that identifies consecu
tive sections of bits as logical subword entities manipulated
by the programmer. The type system explicitly tracks bit
wise operations in the program, and propagates flow of bit
values across the program. The type system captures the
intuition that the programmer is using the masks and shifts
to identify subsections of the bits.
Consider a virtual memory system where virtual addresses
are 32bit values, organized as a 20 bit page table index, a 10
bit offset into a page (for the address), and two permission
bits. The programmer finds the index and the offset using
bit masks:
Page 2
01
02
index =
offset = (x & 0xFFC) >> 2;
(x & 0xFFFFF000) >> 12;
and in addition, can check the permission bits:
03
04
can_read
can_write = (x & 0x2);
= (x & 0x1);
Our type system generates constraints on subfields of the
32bit quantities defined in the program. For example, the
assignment on line 01 constrains the representation of index
to be a subtype of the rhs, and the bitmask on the rhs con
strains the representation of x to have a 20bit part and
a 12bit part (which may be further refined by other con
straints). By generating and solving all such constraints, we
infer the bitlevel types for virtual addresses:
?index,20??offset,10??wr,1??rd,1?
representing a structure with an index field (of 20 bits), an
offset field (of 10 bits), and two permission bits.
In a second step, the types identified by our algorithm are
used to “compile away” the bitvector operations, by trans
lating bitvectors into structures, and translating masks and
shifts into field accesses. The resulting program contains
only assignments between integer variables and boolean
tests, for which existing decision procedures [6, 20] are ex
tremely fast. For bitwise operations that do not conform
to this access pattern (e.g., in signal processing operations,
security algorithms, or in condition codes where each bit in
dependently maintains some boolean information), the type
of a bitvector is just the sequence of individual bits. In this
case, we are no worse than the boolean reasoning obtained
by reducing the word into bits.
We have implemented the bitvector type inference and
program transformation algorithm, and we have applied the
transformation to check safety properties of C programs us
ing the Blast model checker [10]. Blast uses decision pro
cedures for equality and arithmetic. Our previous attempt
to use specialized decision procedures for bitvectors did not
scale to large programs. The decision procedures were un
able to infer and exploit the fact that the common idiomatic
use of bitlevel operations is to use words as packed struc
tures, and thus, were very inefficient. This work is the result
of our attempt to build an analysis that discovered what
highlevel relationships were buried beneath the bitvector
operations.
In our experiments, we took two implementations: one a
memory management and protection framework (Mondrian
[22]) and one a Linux device driver [4]. We first performed
the bitvector type inference on these programs and trans
lated them automatically to programs without bitvector op
erations based on the types. We then applied the Blast
model checker to check safety properties on the translated
programs. The Mondrian implementation was annotated
with a set of assertions by the programmer. For the driver,
we considered five safety properties identified in [11]. Both
programs involve nontrivial bitwise operations. In a pre
vious study [11], the operations from the driver were re
moved by hand. To the best of our knowledge, the Mon
drian implementation was not automatically checked before.
Blast was able to prove 12 of the 15 properties checked. In
contrast, the bitreduction approach implemented in Blast
that applied boolean reasoning to individual bits did not
finish for these examples. The remaining three properties
involved reasoning about multidimensional arrays on the
heap, which is a limitation of Blast and an important, but
orthogonal, problem.
While our primary application was to lift bits to highlevel
structures, bitvector types are relevant even if the analy
sis is carried out at the boolean level, using for example
SAT solvers or binary decision diagrams (BDDs). In these
cases, the bitvector type determines a good variableordering
heuristic. Precisely, variables with the same bitvector type
should be interleaved in the variable order, while variables
that have different bit types can be independently ordered.
This can make an exponential improvement even in simple,
and common, cases. Consider the following program where
all variables are 32bit integers:
y = (x & 0xFFFF0000)>>16;
z = (x & 0x0000FFFF);
y = y  (z << 16);
The bitvector types of the variables are
x
y
z
?a,16??b,16?
?b,16??a,16?
016?b,16?
indicating that values from the top 16 bits of x and the
bottom 16 bits of y may flow into each other, and values
from the low 16 bits of x, the low 16 bits of z, and the high
16 bits of y may flow into each other. This suggests the
variable ordering where the high 16 bits of x and the low 16
bits of y are interleaved, and the low 16 bits of x, the high
16 bits of y, and the low 16 bits of z are interleaved. With
this ordering, the size of the BDDs is linear in the number
of bits. However, with the natural ordering where all bits of
x are ordered before all bits of y and all bits of z, the BDDs
are exponential in the number of bits.
Our inferred types can also be used for memory opti
mizations in resource constrained systems [16, 21]: for a
variable that has a bitvector type with k bits, the compiler
can “pack” several allocations into one word. For variables
where certain bits are unused, the compiler need not allocate
those bits at all.
Related Work.Our typebased approach was directly in
spired by the representation inference of Lackwit [18]. Our
type system and inference algorithm is very similar to those
presented by Ramalingam et. al. for aggregate structure
inference in Cobol [19, 13]. Both the above use type infer
ence to identify structure in weakly typed languages. Our
contribution is to apply similar inference techniques at the
subword level in C programs, and apply to the task of veri
fying code that uses lowlevel operations. This setting poses
the additional challenge of developing a notion of subtyping
that captures the idiomatic uses of bitwise operations, which
is essential for not splitting the structures too finely. By pro
ducing constraints on the bitlevel representations, bitlevel
types provide a means to identify applicationlevel abstrac
tions used by the programmer. The resulting types are an
important program understanding tool that replaces compli
cated bitwise operations in the code with record structures
and field accesses. By focusing on constraints derived from
actual use, we are able to more accurately identify abstract
structures, and highlevel relationships between the sub
words packed inside the integer, that are beyond the scope
of the typedef facility provided by C at the source level
and used by programmers to convey abstractions. While
Page 3
Expressions
e
::=c  x  x[e]  e1⊕ e2
 e&c  ec  e>>n  e<<n
where c,n ∈ N, 0 ≤ n < N
e1 ≤ e2  ¬p  p1∧ p2  p1∨ p2
x:=e  x[e]:=e1  s;s
 if (p) then s else s
 while (p) s
Predicates
Statements
p
s
::=
::=
Figure 1: Program Syntax
the translation does not preserve the runtime memory lay
out, we have found bitlevel types to be an attractive tool
to examine, understand and check the usage of bit fields in
unknown programs and the flow of values between different
bit sections in different variables.
Our work is also similar to [9], where a dataflow analysis
is provided to identify bit sections in programs. This infor
mation is used by the compiler to allocate only the required
number of bits, thereby optimizing memory allocation. Our
type system enables us to formalize many of the proper
ties that were informally stated in [9], to extend uniformly
to complex source level features such as pointers and func
tions and subtyping, to provide principal typing (or “best
possible” representation) guarantees, and cleanly prove the
correctness of the algorithm using standard type inference
techniques. Further, we provide an explicit bound on the
complexity of the algorithm (quadratic in the size of the
program and in the number of bits). While quadratic in the
worst case, in practice this has never been a bottleneck and
all our type inference experiments ran within a few seconds.
2.
2.1
BITLEVEL TYPES
Programs
We demonstrate our algorithm on an imperative language
of while programs with bitlevel operations. Let X be a set
of program variables. For each array variable x in X, we
include two variables x idx and x elem in X (which will be
used as the “index” and “contents” of the array, respec
tively). Figure 1 shows the syntactic elements of our pro
grams. For ease of exposition, we shall assume all variables
and constants have N bits. Expressions are either integer
constants, or Nbit bitlevel constants, or Nbit variables (x)
or array accesses (x[e], where x is an array variable and e its
index), arithmetic operations e1⊕ e2, and the bitlevel op
erations bitwiseand with a constant (e&c), bitwiseor with
a constant (ec), and right or left shift by a constant (e>>n
and e<<n respectively). Predicates are built using arith
metic comparison and the boolean operators. Statements
comprise assignments (to integer variables or array fields),
sequential composition, conditionals, and while loops. Let
Exp(X) denote the set of all expressions using the variables
X. The operational semantics for the language is defined
in the standard way, using a store mapping each variable
to a bit vector and each array to an array of bitvectors and
interpreting the operations in the usual way.
Example 1: [Program] On the left in Figure 2, we show a
program that uses bitlevel operations. We assume all values
have 32 bits. The program mget abstracts (and simplifies)
a kernel’s physical memory access routine.
bit virtual address p as an input. We assume memory is
organized into a one level page table, where each page is 1K
It gets a 32
bytes. We model the page table as an array tab. The page
table is indexed using the top 20 bits of a virtual address.
The next 10 bits of the virtual address is the offset into
the page. The last bit of p is a permission bit, that must
be set in order to access memory at the physical address.
The second last bit of p is a dirtybit that is not relevant to
the current program. If the rightmost permission bit of p is
not set, then the function mget returns an error condition
(permission failure). The variable p1 drops the last 12 bits
from the virtual address p, and the variable b1 shifts the top
20 bits to the right, this is used as an index into the page
table tab. The page table entry returns the base address
b1 of a memory page where the address resides, the variable
base zeroes out the last permission bit. The offset into the
page table is stored in variable off that zeroes out all but the
10 offset bits of p. Finally, the address in memory is obtained
by adding the offset to the base address of the memory page,
and actual memory is looked up by looking at the index a of
a global array m that represents physical memory. We ignore
error conditions and array bounds checks for clarity.
2
2.2Types
We introduce special bitlevel types that are refinements of
the usual base types which describe how the corresponding
Nbit expressions are used in the program as packed records.
Consider the example of Figure 2. We want the bitlevel
type to specify that the virtual address p is used as a packed
record structured as a segment with 20 bits, followed by a
segment with 10 bits, followed by two onebit segments.
Let A be an infinite set of names. A block is a pair ?a,??,
where a ∈ A and ? is an integer in {1...N}. We call a
(resp. ?) the name (resp. size) of the block. We write B for
the set of all blocks. For a block b, we write bkfor a the
sequence of k repetitions of b. A bitlevel type is a sequence
of blocks¯b ≡ ?a1,?1?...?an,?n?. The size ¯b of a bitlevel
type isP
significant bit) to 0 (the rightmost or least significant bit).
We write Typ(?) for the set of all bitlevel types of size ?.
i?i. A bitlevel type¯b encodes a sequence of bits
of length ¯b, indexed from ¯b − 1 (the leftmost or most
Example 2: [Bitlevel Types] The top row in Figure 3(a)
shows a bitlevel type:
¯b ≡ ?e,20?.?a,10?.?k,1?.?u,1?
of size 32 that encodes a virtual address, with a 20bit page
table index, a 10bit offset, and two permission bits. Above
the blocks are integers indicating the ranges of bits occupied
by each block. The page table index occupies section from
the 31st bit to the 12th (inclusive), the offset the section
from the 11th to the 2nd, and the two permission bits are
the 1st and 0th bits.
2
Zero Blocks. We shall assume the set of names A contains
a special name 0, which corresponds to blocks whose bits
have value 0. Hence, the zero block ?0,1? intuitively corre
sponds to a block of 1 bit whose value is 0. We abbreviate
the block ?0,1? to 0; thus 0krepresents k copies of the block
?0,1?, i.e., k consecutive 0 bits.
Projections. For a bitlevel type¯b ≡¯b?.?a,?? and an integer
i we define the blockfragment of¯b at position i as:
(¯b?[i − ?]
(a,i,? − i)
¯b[i] ≡
if ? < i + 1
o.w.
(1)
Page 4
Program
mget(u32 p){
if ((p & 0x1) == 0){
error("Permission Failure");
} else {
p1 = p & 0xFFFFF000;
Zero Constraints Inequality Constraints
τp&1[31 : 1] = 0τp[0 : 0] ≤ τp&1[0 : 0]
τp&120012[11 : 0] = 0τp[31 : 12] ≤ τp&120012[31 : 12]
τp&120012 ≤ τp1
τp1[31 : 12] ≤ τp1> >12[19 : 0]
τp1> >12 ≤ τpte
τpte ≤ τtab idx
τtab elem≤ τb1
τb1[31 : 2] ≤ τb1&13002[31 : 2]
τb1&13002 ≤ τbase
τp[11 : 2] ≤ τp&11002[11 : 2]
τp&11002 ≤ τoff
τbase ≤ τbase+off
τoff ≤ τbase+off
τbase+off ≤ τa
τa ≤ τm idx
τm elem≤ τmget r
pte = p1 >> 12;
τp1> >12[31 : 20] = 0
b1 = tab[pte];
base = b1 & 0xFFFFFFFC;
τb1&13002[1 : 0] = 0
off = p & 0xFFC;
τp&11002[1 : 0] = 0
τp&11002[31 : 12] = 0
a = base + off;
return m[a];
}
}
Figure 2: Example Program, Constraints
Intuitively, the blockfragment of¯b at position i is a triple
(a,?,??) such that the ith bit of¯b lies in a block ?a,? + ???,
with ??bits to the left of the ith bit in ?a,? + ???, and ? bits
to the right. Equivalently,¯b has as a suffix the sequence
?a,? + ???.¯b??where the size of¯b??is i−?. In particular, if the
blockfragment at position i is (a,?,0) then¯b has as a suffix
the sequence ?a,??.¯b??of size i. In this case, we say that¯b
has a break at position i.
For a bitlevel type¯b and a pair of integers i,j we define
the projection of¯b to the interval [i : j] as:
8
>
where ⊥ means the value is undefined and the concatenation
. is ⊥ if either argument is ⊥. When defined, the projection
of¯b to [i : j] is a bitlevel type¯b?of size i − j + 1, such that
¯b is of the form¯b+.¯b?.¯b−where the size of¯b+(resp.¯b−) is
N − i (resp. j). In other words, the projection is a bitlevel
type corresponding to the sequence of blocks beginning at
position i and ending at position j of¯b. The projection of¯b
to the interval [i : j] is defined iff¯b has breaks at i + 1 and
j.
¯b[i : j] ≡
>
:
<
?
?a,??.¯b[i − ? : j]
⊥
if i < j
if¯b[i + 1] = (a,?,0)
o.w.
Example 3:
row of Figure 3(a) shows the blockfragment of¯b (from the
top row) at position 8. Notice that¯b does not have a break
at that position: in particular, the block ?a,10? occupies the
section from the 11th to the 2nd bit, and hence there are 6
bits of ?a,10? “remaining” on the right, at position 8, and a
prefix of 4 bits on the left. Hence¯b[8] is (a,6,4). The third
row of Figure 3(a) shows the blockfragment of¯b at posi
tion 12. Notice that¯b has a break at position 12 as there
are three whole blocks to the right. Hence¯b[12] is (a,10,0),
which in turn indicates that a block ?a,10? starts at the po
sition 12 of¯b. The fourth row of Figure 3(a), shows the
projection of¯b to the interval [11 : 1], which is the sequence
of blocks ?a,10??k,1?. It is welldefined as¯b has a break at
[BlockFragments, Projections] The second
position 11 + 1, and hence¯b[11 + 1] = (a,10,0), and also
has a break at position 1. The projection is the sequence of
blocks occupying the 11ththrough the 1stbits (inclusive).
2
Subtyping.
¯b1 ≤¯b2 if either 1)¯b1 =¯b2, or 2)¯b1 ≡ 0?.?a,???.¯b?
¯b2 ≡ ?a,? + ???.¯b?
each i such that¯b2 has a break at position i, it is the case
that¯b1 also has a break at that position, and moreover, if
the block of¯b2at that position is ?a,?? then at that position,
¯b1 has a sequence of zero blocks followed by a block ?a,???
such that the length of the entire sequence is ?.
To get an intuitive understanding of our subtyping rela
tion, note that if ? ≤ ??then
0N−??a,?? ≤ 0N−???a,???
as the LHS corresponds to integers less than 2?which are
a subset of integers less than 2??
on the RHS corresponds to.
sider 0N−?−k?a,??0kto be a subtype of 0N−???a,???, when
k > 0 because in the former type, the nonzero segments
are not rightaligned. This captures the idiomatic use of
subtypes, wherein the programmer ensures rightalignment
(via a rightshift) before assignment. Our subtyping def
inition generalizes this observation to bitlevel types that
contain multiple blocks, i.e. where the bitvector contains
more than one packed integer integer. It is easy to check
that the subtyping relation is a partialorder.
For two bitlevel types¯b1,¯b2, we say that
1,
2, and¯b?
1≤¯b?
2. Intuitively,¯b1 ≤¯b2 if for
which is what the type
However, we do not con
Example 4: [Zero blocks and Subtyping] The lower half
of the top row of Figure 3(b) shows a bitlevel type¯b1 ≡
010.?p,6?.06?q,10? containing zero blocks. The upper half
shows a type¯b2 ≡ ?p,16?.?q,16?, such that¯b1 ≤¯b2.
Sound Type Assignment. A Xtype assignment is a map
Γ : X → Typ(N) that assigns assigning a bitlevel type
to each element of X. For an Xtype assignment Γ, we
write Γ ? e :¯b to denote that under the assignment Γ the
2
Page 5
Figure 3: (a) Bitlevel types and Projections (b) Substitution and Subtyping (c) Bitlevel type for mget
Γ(x) =¯b
Γ ? x :¯bVar
Γ(x idx) =¯b Γ(x elem) =¯b?
Γ ? x[e] :¯b?
Γ ? e :¯b
Arr
Γ ? e :¯b
¯b ≤¯b?
Γ ? e :¯b?
Sub
Γ ? e : τ1
τ[h,l] = τ1[h,l] for [h,l] ∈ Brk(c,1)
Γ ? (e  c) : τ
BitOr
Γ ? e : τ1
τ[h,l] = 0h−lfor [h,l] ∈ Brk(c,0)τ[h,l] = τ1[h,l] for [h,l] ∈ Brk(c,1)
Γ ? (e&c) : τ
BitAnd
Γ ? e :¯b
Γ ? (e>>c) : 0c.¯b[N : N − c]Rshift
Γ ? e1 :¯b
Γ ? e :¯b
Γ ? (e<<c) :¯b[N − c : 0].0cLshift
Bound(e1⊕ e2,?)
AOp
Γ ? e2 :¯b
¯b = 0N−??a,??
Γ ? (e1⊕ e2) :¯b
Leq
Γ ? e1 :¯bΓ ? e2 :¯b
Γ ? (e1 ≤ e2)
¯b?≤¯b
¯b = 0N−??a,??
Γ ? p1
Γ ? (p1∧ p2)
Γ ? p2
BOp
Γ ? l :¯bΓ ? e :¯b?
Γ ? l := e :¯b?
Γ ? s1
Γ ? if p then s1 else s2
Asgn
Γ ? s1
Γ ? s2
Γ ? s1;s2
Γ ? p
Γ ? while (p) sWhile
Seq
Γ ? pΓ ? s2
Cond
Γ ? s
Figure 4: Typing rules
expression e has type¯b, and we write Γ ? s to denote that
Γ is sound w.r.t. the program s. The rules that derive these
judgements are shown in Figure 4.
The key rules are the assignment rule (Asgn), which re
quires that the lvalue assigned to be a supertype of the value
to which it is assigned, the standard subtyping subsumption
rule (Subs), the rules that relate the types of bitlevel ex
pressions to their subexpressions, and the rules for arith
metic operations (AOp).
A bitlevel type¯b is called single, if it is of the form
0??a,???.
operands have the same type, which must be a single type,
and the result expression must be within the correspond
ing number of bits. The requirement that the result of an
arithmetic operation does not overflow the number of bits
available in the block ?a,??? can be established by either stat
ically proving the absence of overflows or inserting runtime
checks [7]. This requirement is stipulated by the predicate
Bound(e,?) which states that e ≤ 2?, in the hypothesis of
the rule for arithmetic operations AOp.
For the rules BitAnd and BitOr, that describe the
types of bitwise expressions in terms of their subexpres
sions we use the following notation. For a constant c, and
For arithmetic operations, we require that the
b ∈ {0,1}, let Brk(c,b) be the set of maximal consecutive
bintervals in c, that is, Brk(c,b) = {[h1 : l1],...,[hk: ll]},
where for each i ∈ {1,...,k}, we have that the bits in the
interval [hi : li] are all b, the (li−1)−th and the (hi+1)−th
bits (if they are in the range [0,N − 1]) are 1 − b, and all
other bits of c are 1 − b.
Sound typings capture the intuition of when bitvectors
are correctly used in a program. It ensures the correct us
age of packed fields within a bitlevel (when the bitlevel
is used as a record), and it only allows arithmetic opera
tions when there is effectively only one field of a record thus
avoiding arithmetic overflows that go into the next fields. If
the program can be soundly typed then we can translate the
program into an equivalent program where the (packed) in
tegers are replaced with records and the bitwise operations
are replaced with the appropriate field selectors as described
in Section 4.
Example 5:
ure 3(c) demonstrates a sound bitlevel typing. These types
correspond to the use of virtual addresses that are struc
tured as a 20bit index, a 10bit offset, a dirty bit, and a
permission bit.
For the program mget from Figure 2, Fig
2
Page 6
3. TYPE INFERENCE
The type inference problem for bitvector typings takes as
input a program and generates a sound bitvector typing. We
adopt a constraint based approach, where the type inference
problem is solved by generating and solving a system of
constraints from the program.
3.1Constraints and Solutions
Constraints. We shall use type variables τ drawn from the
set T to denote the (unknown) bitvector types of various
program expressions. There are two kinds of constraints:
1. A zero constraint of the form:
τ[i : j] = 0
where i,j ∈ {0,...,N − 1} are such that N > i − j ≥
0. Zero constraints stipulate that the projection of the
bitvector type corresponding to τ to the interval [i : j]
be a sequence of i − j + 1 zero blocks.
2. An inequality constraint of the form:
τ[i : j] ≤ τ?[i?: j?]
where i,j,i?,j?∈ {0,...,N − 1} are integers such that
N > i−j = i?−j?≥ 0. Inequality constraints stipulate
that the values of τ,τ?be bitvector types¯b,¯b?such that
the projection of¯b to the interval [i : j] be defined and
a subtype of the projection of¯b?to the interval [i?: j?].
We use τ ≤ τ?to abbreviate τ[N−1 : 0] ≤ τ?[N−1 : 0].
Solutions. A type assignment is a map T from T to Typ(N).
An assignment T satisfies the zero constraint τ[i : j] = 0 if
T(τ)[i : j] = 0i−j+1. We say that an assignment T satisfies
the inequality constraint τ[i : j] ≤ τ?[i?: j?] if T(τ)[i : j] ≤
T(τ?)[i?: j?]. We write T = c to say that T satisfies the
constraint c. Notice that the assignment λτ.0Ntrivially
satisfies every constraint, but nevertheless will be useless
for our purposes.
Let C be a set of zero and inequality constraints. We say
that T is a zero assignment for C if:
1. T satisfies all zero constraints of C, and,
2. For every τ?and integer k such that N > k ≥ 0,
if T(τ?)[k] = (0,1,0) then either (a) there is a zero
constraint τ?[i?: j?] = 0 where i?≥ k ≥ j?, or (b) there
is an inequality constraint τ??[i??: j??] ≤ τ?[i?: j?] for
some τ??, i??, j??where i?≥ k ≥ j?.
We say that T is a principal zero assignment for the con
straints C if for all type variables τ, and all N − 1 ≥ i ≥ 0,
we have T(τ)[i] = (0,1,0) if there exists any zero assign
ment T?for C such that T?(τ)[i] = (0,1,0). We say that
T is a solution for the constraints C, written T = C, if
T is a principal zero assignment for C that satisfies every
inequality constraint in C.
Intuitively, a principal zero assignment assigns a zero
block to an interval of a type variable if it can be inferred
from the constraints that at runtime the segment will al
ways have zeros. This may be either because of zero con
straints which arise due to left shifts, right shifts or bitmasks,
or because (transitively) all the values assigned to the cor
responding expression are guaranteed to have zeros in that
interval.
Example 6:
The two right columns of Figure 2 show sets of zero and in
equality constraints, respectively. The first row of Figure 7
shows a zero assignment T0 for the constraints of Figure 2.
Notice that T0(τp&120012) ≡ 020?q,10?02, i.e., the first twenty
and last two bits (from the left) are zeros, as stipulated by
the constraints τp&120012[31 : 12] = 0 and τp&11002[1 : 0] = 0.
Also as there are no inequality constraints for τtab elem, we
have T0(τtab elem) ≡ ?m,32?, and similarly for τb1 there are
no zero blocks. However, due to the bitmask, T0(τb1&13002),
and hence T0(τbase) has two zero blocks, as the constraint
τb1&13002 ≤ τbase is the only inequality constraint with τbase
on the right hand side. Finally, τa only has two zero blocks
in the 0−th and 1st bits, as only those are “common” to
τbase and τoff. The last row of Figure 7 shows a solution T
w.r.t. the column variables for the constraints of Figure 2.
As the assignment “agrees” with T0 on the zero blocks, it is
also a zero assignment. It is easy to check that every con
straint is satisfied by the assignment. For example, notice
as 020?a,10?02≤ ?a,30?02, the assignment satisfies the con
straint τoff ≤ τa.
[Constraints, Zero Assignments, Solutions]
2
Substitutions. A substitution is a map σ from A \ {0} to
Typ(N), such that σ(a) contains no zero blocks. We write
[a ?→¯b] for the substitution λx.(x = a ?¯b : ?x,N?) which
maps the name a to¯b and maps all other names x to a
bitvector type with only one block ?x,N? of size N. A sub
stitution σ is extended to a map from blocks (B) to bitvector
types (B+) as follows:
σ(?a,??) ≡ ?b,???.¯b[? − ??− 1 : 0]
where σ(a) =¯b and¯b[? − 1] ≡ (b,??,·).
substitution [a ?→¯b] maps the block ?a,?? to the sequence of
blocks that is the suffix of¯b of size ?, i.e., the sequence of
blocks corresponding to the rightmost ? bits of¯b.
Intuitively, the
Example 7:
?p,16?. When the substitution [p ?→ ?r,24?.?s,8?] is applied
to it we get the bitlevel type: ?r,8?.?s,8? corresponding to
the rightmost 16 bits of the map image. Similarly, when the
substitution [q ?→ ?t,24?.?u,8?] is applied to ?q,10? we get
the bitlevel type ?t,2?.?u,8? corresponding to the last 8 bits
of the map image.
[Substitutions] Consider the bitlevel type
2
We extend the substitution σ to a map from bitvector
types (B+) to bitvector types (B+) as:
σ(?)
σ(?a,??.¯b?)
≡
≡
?
σ(?a,??).σ(¯b?)
Finally, we extend substitutions to type assignments T as:
σ(T) ≡ λτ.σ(T(τ))
The key property of substitutions is that they preserve
constraint satisfaction.
Theorem 1. [Substitution] For all sets of constraints
C, type assignments T and substitutions σ, if T = C then
σ(T) = C.
Example 8: [SubstitutionPreservation] In the top half of
Figure 3(b), we have an assignment T where that satisfies
the constraint τ1 ≤ τ2. The lower half shows an assignment
T?which is the result of substituting q with¯bq ≡ ?t,24??u,8?
Page 7
and p with¯bp ≡ ?r,24??s,8?. Note that ?p,16? gets substi
tuted with ?r,8??s,8? corresponding to the suffix of size 16
of¯bp. However, ?p,6? is mapped to the rightmost 6 bits of¯bp
i.e., ?s,6?. Similarly, note that ?q,16? gets substituted with
?t,8??u,8? which is the suffix of size 16 of¯bq. On the other
hand, ?q,10? gets mapped to the rightmost 10 bits of¯bq,
namely ?t,2??u,8?. Notice that the substituted assignment
T?also satisfies the constraint τ1 ≤ τ2.
Principal Solutions. For two type assignments T,T?, we
say that T ? T?if there exists a substitution σ such that
T?= σ(T). A solution T for a set of constraints C is called
the principal solution if for all T?= C, we have T ? T?.
In order to infer types, we will generate constraints on
the types of each program expression, and then compute
the principal solution for the constraints.
3.2Constraint Generation
To generate constraints, we introduce for every expression
e in the program, a type variable τe. For array variables
x, we have two type variables, τx idx and τx elem. We then
generate constraints on the type variables that capture the
typing relationships between expressions and subexpressions
shown in Figure 4.
Figure 5 shows the constraint generation rules from the
program syntax. The constraint generation rules are given
as a recursive procedure CGen that generates a set of con
straints from a program. The procedure CGen uses the re
cursive procedures CGenP and CGenE as helpers to generate
constraints from predicates and expressions respectively.
2
Theorem 2. Let C = CGen(P) for a program P, and let
T be a solution of C. Then the typing λe.T(τe) is a sound
bitvector typing of P.
3.3 Inference Algorithm
We now show that every set of constraints has a princi
pal solution by giving a twostep algorithm that computes
the principal solution for a set of constraints. We give the
conceptual algorithm in this section. We describe efficient
graphbased data structures to implement this algorithm in
the next section.
Step 1: Initial Zero Assignment. In the first step, using
all the constraints, we determine which parts of the various
bitvector types must be zero blocks. In particular, we com
pute using Algorithm T0, a principal zero assignment T0
such that for every T = C, we have T0 ? T. The Algo
rithm T0 is a simple dataflow analysis algorithm that first
processes each zero constraint τ[i : j] and for each such con
straint, splitting the type for τ at positions i,j and setting
the interval between the positions to i−j+1 zero blocks, and
then propagates the zeros across inequality constraints until
a fixpoint is reached. To get a principal zero assignment, a
type variable τ appearing on the right hand side of an in
equality constraint, gets an interval set to zeros only if for
each inequality constraint where τ appears on the RHS, the
corresponding LHS type variables have zeros in the corre
sponding intervals. All the remaining nonzero blocks have
distinct names in the principal zero assignment. We omit a
full description of the algorithm for space reasons.
Example 9: [Principal Zero Assignment] The top row of
Figure 7 shows the principal solution to the constraints from
Figure 2. Notice that the fourth zero constraint sets the
interval [1 : 0] for τb1&13002 to zero, which is due to the bit
mask operation. As the only inequality constraint with τbase
on the RHS is τb1&13002 ≤ τbase, the zeros in the interval
[1 : 0] are propagated to τbasemaking its two least significant
bits zeros. Similarly the constraints τp&11002[1 : 0] = 0 and
τp&11002[31 : 12] = 0 cause the zero assignment 020?q,10?02
for τp&11002, and the inequality constraint τp&11002 ≤ τoff, the
only inequality constraint with τoff on the RHS, propagates
the zeros to τoff giving it the type 020?o,10?02.
2
Proposition 1. [Principal Zero Assignment] For ev
ery set of zero and inequality constraints C, the procedure
T0(C) in time O(CN) returns a principal zero assignment
T0 such that for every zero assignment T for C, we have
T0 ? T.
Step 2: Refinement. In the second step, we iteratively
refine the “principal” zero assignment T0, until each of the
inequality constraints are satisfied. This is done using the
functions Split and Unify.
Algorithm 1 Split
Input: Type assignment T, Type Variable τ, Interval [i : j]
Output: A type assignment T
if T(τ)[i + 1] = (a,?2,?1) and ?1 > 0 then
T := [a ?→ ?b1,N − ?2??b2,?2?](T)
if T(τ)[j] = (a?,??
T := [a??→ ?b?
return T
{b1,b2 fresh}
{b?
2,??
1,N − ??
1) and ??
2??b?
1> 0 then
2,??
2?](T)
1,b?
2fresh}
Split(T,τ,[i : j]). The function Split shown in Algorithm 1
takes an assignment T, a constraint variable τ, and an in
terval [i : j], and returns an assignment T?such that T ? T?
and T?(τ)[i : j] is defined, i.e., T?(τ) has breaks at i+1 and
j. Intuitively, function Split splits the bitvector type T(τ)
at the end points of the interval. The new type assignment
returns this split type for τ, and returns the type T(τ?) for
all other variables τ?.
Algorithm Split first finds the block fragment of T(τ) at
position i + 1. If there is already a break at position i + 1,
there is nothing to do. However, if there is no break at
that position, i.e., the block fragment is (a,?,??) and ??> 0,
then a new break is introduced by substituting the block a
at that position with ?b1,N − ???b2,?? where b1,b2 are two
fresh names. The resulting assignment is guaranteed to have
a break at i + 1. The same process is repeated (using the
new assignment for τ with a break at i+1) for the position
j. The algorithm returns the type assignment that results
after the second substitution.
Example 10: [Split] The left panel of Figure 6 illustrates
how Split(T,τp,[11 : 2]) works on an assignment T where τp
is mapped to ?p,32?. First, we enforce a break at position
11 + 1. As T(τp)[11 + 1] is (p,12,20), we deduce there is
no break and enforce it by substituting p with ?e,20??f,12?.
Next, in the resulting assignment, we enforce a break at
position 2. Now T(τp)[2] is (f,2,10) and so we substitute
f with ?g,30??h,2?, and hence get an assignment T where
T(τ) has breaks at 11+1 and 2 and thus, where T(τ)[11 : 2]
is defined.
2
Unify(T,τ[i : j] ≤ τ?[i?: j?]). The function Unify shown in
Algorithm 2 takes a zero assignment T, and an inequality
Page 8
(Expressions CGenE)x
x[e]
c
e<<c
e>>c
e&c
{}
CGenE(e) ∪ {τe ≤ τx idx}
{}
CGenE(e) ∪ {τe[N − 1 − c : 0] ≤ τe< <c[N − 1 : c],τe< <c[c − 1 : 0] = 0}
CGenE(e) ∪ {τe[N − 1 : c] ≤ τe> >c[N − 1 − c : 0],τe> >c[N − 1 : N − 1 − c] = 0}
CGenE(e) ∪ {τe[hi : li] = τe&c[hi : li]  [li,hi] ∈ Brk(c,1)}
∪{τe&c[hi : li] = 0  [li,hi ∈ Brk(c,0)}
CGenE(e) ∪ {τe[hi : li] = τec[hi : li]  [li,hi] ∈ Brk(c,1)}
CGenE(e1) ∪ CGenE(e2) ∪ {τe1≤ τe1⊕e2,τe2≤ τe1⊕e2}
CGenE(e1) ∪ CGenE(e2) ∪ {τe1≤ τe1≤e2,τe2≤ τe1≤e2}
CGenP(p1) ∪ CGenP(p2)
CGenP(p1) ∪ CGenP(p2)
CGenP(p)
e  c
e1⊕ e2
e1 ≤ e2
p1∧ p2
p1∨ p2
¬p
x := e
x[e1] := e2
s1;s2
if(p)thens1elses2
while(p)s
(Predicates CGenP)
(Statements CGen)
CGenE(e) ∪ {τe[N − 1 : 0] ≤ τx[N − 1 : 0]}
CGenE(x[e1]) ∪ CGenE(e2) ∪ {τe2[N − 1 : 0] ≤ τx elem[N − 1 : 0]}
CGen(s1) ∪ CGen(s2)
CGenP(p) ∪ CGen(s1) ∪ CGen(s2)
CGenP(p) ∪ CGen(s)
Figure 5: Constraint generation algorithm CGen(P)
Figure 6: Split and Unify
constraint c ≡ τ[i : j] ≤ τ?[i?: j?] such that T(τ)[i : j] and
T(τ)[i?: j?] are both defined, and returns an assignment T?
such that T ? T?and T?= c. It proceeds by ensuring that
the block at position i of T(τ) is the same as that of T(τ?) at
position i?, by appropriate substitutions, and then recursing
on the remaining suffixes.
Example 11: [Unify] The middle panel of Figure 6 shows
the first example of Unify. We wish to enforce the constraint
τp1[31 : 12] ≤ τp1> >12[19 : 0], given an assignment T where
the bitvectors corresponding to the LHS and RHS of the in
equality are respectively ?d,20?? ,12? and ? ,12??e,20?. The
block fragments of T(τ) (resp. T(τ?)) at position 31+1 (resp.
19 + 1) are (d,20,0) and (e,20,0) and the second condition
matches and the function substitutes d with ?e,32? in T and
recurses on τ[31 − 20 : 12] ≤ τ?[19 − 20 : 0] which triv
ially holds. A second example of Unify is shown in the right
panel of Figure 6. Here, we wish to enforce the constraint
τoff[31 : 0] ≤ τa[31 : 0]. The block fragment for the LHS
are (0,1,0) (and so the first condition matches) until the
recursion has shrunk the constraint’s interval to [11 : 0]. At
this point, the LHS block fragment is (o,10,0) and the RHS
is (a,10,20). Hence, the second condition matches and we
substitute o with ?a,32? and thus enforce the constraint. If
instead the RHS fragment had been a12?, with ? > 10 then
the third condition would match and the function would
have substituted a with some fresh ?b,N − ???b?,??, thus
ensuring that in the subsequent recursion, the second condi
tion matched and it sufficed to substitute b with o to enforce
the constraint.
For an inequality constraint c ≡ τ[i : j] ≤ τ?[i?: j?] we
introduce the following abbreviation:
2
Refine(T,c) ≡ Unify(Split((Split(T,τ,[i : j]),τ?,[i?: j?])),c)
Proposition 2. [Refine] For any assignment T and
equality constraint c, Refine(T,c) returns an assignment T?
such that (1) T?= c, and, (2) for every T ? T??such that
T??= c, we have T?? T??.
Using the above, we can compute the principal solution
for a set of constraints by iteratively refining the initial zero
assignment. This is made precise in the algorithm shown in
Figure 3.
Proposition 3. [Solve] For every set of inequality con
straints C, and assignment T, the assignment T?
Solve(C,T) is such that (1) T?= C, and, (2) For every
T ? T??such that T ? T??, we have T?? T??.
≡
Page 9
Algorithm 2 Unify
Input: Type Assignment T, Constraint c ≡ (τ[i : j] ≤
τ?[i?: j?])
Output: A type assignment T?s.t. T ? T?and T?= c
if i < j then
return T
match T(τ)[i],T(τ?)[i?] with
 (0,1,0),

 (a,?, ),(a?,??, ) when ??= ?−→
c?:= (τ[i − ??: j] ≤ τ[i?− ??: j?])
Unify([a ?→ ?a?,N?](T),c?)
 (a,?, ),(a?,??, ) when ? > ??−→
T?:= [a ?→ ?b,N − ? + ????b?,? − ???](T) {b,b?fresh}
Unify(T?,c)
 (a,?, ),(a?,??, ) when ? < ??−→
T?:= [a??→ ?b,N − ??+ ???b?,??− ??](T) {b,b?fresh}
Unify(T?,c)
−→Unify(T,τ[i − 1 : j] ≤ τ?[i?− 1 : j?])
,(0,1,0)−→ assert false
Algorithm 3 Solve
Input: A Set of constraints C, Initial Assignment T0
Output: Principal solution for C
T := T0
while C ?= ∅ do
pick and remove constraint c from C
T := Refine(T,c)
return T
The theorem follows by induction on the size of C. Intu
itively, the assignment after the first k iterations of the loop
is the principal solution for the subset of k constraints of
C that have been processed. The inductive step is proved
using the fact that substitution preserves satisfaction 1 and
the property of Refine given in Proposition 2. Using the
above, and Proposition 1 we get the following result about
principal solutions for bitvector constraints.
Theorem 3. [Principal
straint set C containing the C≤ as inequality constraints,
Solve(C≤,T0(C)) is a principal solution for C and it is com
puted in time O(C2N2).
Solutions] For every con
Example 12: [Solving Constraints] Figure 7 shows how the
constraints C from the example of Figure 2 are solved. The
top row shows the starting zero solution (T0 ≡ T0(C)). Each
of the following rows show how the assignment is refined
so as to satisfy the inequality constraint shown in the first
column of the row. The refines corresponding to the last
two constraints are illustrated in the right panel of Figure 6.
The solution for all the constraints is exactly the bitvector
typing shown in Figure 3(c).
2
3.4Graphbased Constraint Solving
The inference algorithm in the previous section is al
ways quadratic in the number of bits. We now present a
graph data structure to represent type assignments T and
which enables the efficient implementation of the primi
tives on which the procedures of the previous section are
based: namely lookup (T(τ)), finding the blockfragment
at a given position (T(τ)[i]), and substitution (σ(T)). In
particular, the data structure allows us to perform an sub
stitution in constant time, and thus the entire computation
Figure 8: Assignment graphs (a) graph T, (b) graph
T?obtained by expanding T
in time O(C2·W2) where W ≤ N is the number of nonzero
blocks in the final principal solution. Since for most exam
ples, the number of nonzero blocks is much less than the
total number of bits, we expect this algorithm to work better
in practice, and this is the version we have implemented.
An assignment graph is a tuple G ≡
(V,E,A,R) where:
Data Structure.
• V is a set of vertices,
• E is an ordered edge relation, i.e., a map from V to
sequences of successors (V ×N)∗such that for every v ∈
V the sequence E(v) is either the empty sequence ? or a
sequence (v1,?1)...(vk,?k) such thatP
• A is a map from V to A∪ ⊥, such that for every v ∈ V ,
we have A(v) =⊥ iff E(v) ?= ?, and for every name a
there is a unique vertex A−1(a) ∈ V with that name,
and
1≤j≤k?j = N,
• R is a map from type variables τ to vertices V .
A vertex v ∈ V is a leaf vertex if E(v) = ?, it is an internal
vertex otherwise. We shall only consider acyclic assignment
graphs for which there do not exist vertices v1,...,vn and
naturals j1,...,jn−1 such that (vi+1,ji+1) ∈ E(vi) for all
i = 1,...,n − 1, and vn = v1. For every internal vertex
v, we can define the successorfragment of v at position i,
written E(v)[i] in a manner analogous to sequences of blocks
in Equation (1) by replacing names with vertices: that is,
letting E(v) =¯b?· (vk,?k), we have
(¯b?[i − ?k]
(vk,i,?k− i)
Figure 8(a) shows an assignment graph for the types τ1 and
τ2 from Figure 3. We draw the ordered edge relation as a
sequence of edges labeled with numbers, where the ordering
is represented left to right. We do not list leaf vertices with
no incoming edges.
E(v)[i] ≡
if ?k< i + 1
o.w.
Computing T(τ). An internal vertex v of the assignment
graph represents the bitvector type obtained by a preorder
Page 10
Constraint
T0
c1
c2
c3
c4
c5
c6
c7
τp
τp&11002
τoff
τtab elem
?m,32?
τb1
τb1&13002
?r,30?02
τbase
τa
?p,32?
020?q,10?02
020?q,10?02
020?q,10?02
020?o,10?02
020?q,10?02
?n,32??b,30?02
?a,30?02
?e,20??q,10??c,2?
?e,20??q,10?02
“” “”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
“”
?n,32??n,32?
?r,30??d,2?
?b,30??d,2?
?a,30??d,2?
?r,30??d,2?
?b,30??d,2?
?a,30??d,2?
?r,30?02
?b,30?02
?a,30?02
?b,30?02
?a,30?02
?a,30?02
?e,20??a,10??c,2?
020?a,10?02
020?a,10?02
“”“” “”“”“”
Figure 7: Running Solve on Constraints from Figure 2. c1 = τp[11 : 2] ≤ τp&11002[11 : 2],c2 = τp&11002 ≤ τoff,c3 =
τtab elem≤ τb1,c4 = τb1[31 : 2] ≤ τb1&13002[31 : 2],c5 = τb1&13002 ≤ τbase,c6 = τbase ≤ τa,c7 = τoff ≤ τa.
traversal of the ordered edges in the subgraph rooted at v,
and using the map A at leaf nodes. This is formalized by the
function Seq defined below that takes a vertex v and a size
? and returns the sequence of blocks of size ? corresponding
to the suffix of size ? that v represents:
8
>
To find the sequence of blocks corresponding to a type vari
able τ, we compute Seq(R(τ),N), i.e.,
Seq(v,?) ≡
>
:
<
?
?A(v),??
Seq(v?,??) :: Seq(v,? − ??)
if ? = 0
if E(v) = ?
if (v,??, ) = E(v)[? − 1]
T(τ) ≡ Seq(R(τ),N)
For example, the node v0 in Figure 8(a) represents the
bitvector type 010?p,6?06?q,10?, and the node v1 represents
the type ?p,16??q,16?. Notice that the assignment graph en
ables us to share the representations of the bitvector types.
Computing T(τ)[i]. To find the block fragment at a posi
tion i for a type variable τ, we use the function Find defined
below that takes a vertex and a size and returns the triple
corresponding to the block fragment:
(
where (v?,??,???) = E(v)[? − 1]
Now, T(τ)[i] ≡ if E(R(τ)) = ? then (A(R(τ)),?,N − ?) else
Find(R(τ),?).
Computing [a ?→¯b](T). To compute the substitution of
T using the map [a ?→ ?a1,?1?...?ak,?k?] we find the vertex
v = A−1(a), i.e., corresponding to the name a, set A(v) to ⊥,
and set E(v) to the sequence (A−1(a1),?1)...(A−1(ak),?k).
For example, Figure 8(b) shows a typing T?obtained from
the typing T in Figure 8(a) by the substitution [p ?→
?r,24??s,8?][q ?→ ?t,24??u,8?]T. Notice that τ2 ≤ τ1 and
σ(τ2) ≤ σ(τ1) for this substitution σ.
Combining the algorithm Solve from the previous section
with the graph representation, we get the following.
Find(v,?) ≡
if E(v?) = ? then (A(v),??,???) else Find(v?,??)
Theorem 4. For every constraint set C, a principal so
lution for C can be computed in time O(C2W2) where W
is the number of nonzero blocks in the principal solution.
4.TRANSLATION
Given a bitvector typing of a program, we convert in
teger variables on which bitwise operations are performed
into structures, and syntactically replace each occurrence
of bitwise operators in the program by assignment opera
tions to appropriate fields of these structures. The result is
a program where there are no bitwise operations. Specifi
cally, for each bitvector type ?a1,?1?...?ak,?k? assigned to
x
x[e]
e&b
eb
e<<n
e<<n
e1⊕ e2
x.field(x,[i : j])
x[to int(τe,e)].field(x elem,[i : j])
simplify&(translate(e,[i : j]),b[i : j])
simplify(translate(e,[i : j]),b[i : j])
translate(e,[i − n : j − n])
translate(e,[i + n : j + n])
assert(Bound(e1⊕ e2,j − i))
translate(e1,[i : j]) ⊕ translate(e2,[i : j])
Figure 9: Translation Algorithm translate(e,[i : j])
an lvalue in the program, we introduce a structure with
k unsigned integer fields a1, ..., ak (we collapse the zero
blocks into one field). (The conversion to unsigned integer
fields is wasteful in terms of bits, however it is sufficient
for our applications to verification. The translation algo
rithm can easily output packed structures with bit length
annotations.) Each assignment x:=e in the program is then
translated to a structure assignment by assigning to each
field of τx the corresponding bit sequence from e, that is,
if τx == ?a1,?1?...?ak,?k?, we generate the assignments
x.ai = translate(e,[N − 1 −P
fying the appropriate bitsection is performed by the recur
sive algorithm in Algorithm 9 that traverses the structure
of the expression and extracts the relevant bits. It uses two
support functions: the function field(x,[i : j]) returns the
field corresponding to bits [i : j] by projecting the bitvec
tor type for x to [i : j]. The function to int calls a system
defined function to convert bitvector structures to integers.
The bitwise & and  operators are simplified away using the
functions simplify&and simplifyusing the rules xi&0 = 0
and x1 = 1. By the typing rule, the second argument of
simplify is always either all 1s or all 0s. For simplify&, if the
second argument is all 0, the function returns 0, otherwise
the function returns the first argument. Dually, simplifyre
turns the constant 1j−iin case the second argument is all
1, and the first argument otherwise. Notice that a runtime
assert is added for arithmetic operations. These asserts can
be statically discharged by an auxiliary valueflow analysis.
Additionally, for array indices, and to cope with con
structs not in our language (for example, bitwise operations
with variables on the right hand side), we introduce con
version operations to and from unsigned integers for each
structure introduced. These conversion functions perform
the obvious translation from the structure representation to
the corresponding integer and back. However, for our veri
fication application, all we care about is that the functions
are onetoone. This was sufficient for proving our proper
j<i?j : N − 1 −P
j≤i?j] that
identify the corresponding bits of e. The work of identi
Page 11
typedef u32 BOT_t;
typedef u32 k_t; typedef u32 s_t;
typedef u32 b_t; typedef u32 r_t;
struct __b0 { k_t k_1 ; BOT_t BOT_2 ; };
struct __b1 { BOT_t BOT_1 ; k_t k_2 ; };
struct __b2 { b_t b_1 ; r_t r_2 ; };
struct __b3 { b_t b_1 ; BOT_t BOT_2 ; };
struct __b4 { BOT_t BOT_1 ; b_t b_2 ; BOT_t BOT_3 ; };
struct __b5 { k_t k_1 ; b_t b_2 ; s_t s_3 ; };
u32 mget(struct __b5 p ) {
struct __b0 p1; struct __b1 pte;
struct __b2 b1, tab[1000];
struct __b3 base, a; struct __b4 off;
if (p.s_3==0) {
error("Permission failure");
} else {
p1.k_1 = p.k_1; p1.BOT_2 = 0;
pte.BOT_1 = 0; pte.k_2 = p1.k_1;
b1.b_1 = tab[__b1_to_u32(pte)].b_1;
b1.r_2 = tab[__b1_to_u32(pte)].r_2;
base.b_1 = b1.b_1; base.BOT_2 = 0;
off.BOT_1 = 0; off.b_2 = p.b_2; off.BOT_3 = 0;
a.BOT_2 = 0; a.b_1 = base.b_1 + off.b_2;
return m[__b3_to_u32(a)];
}
}
Figure 10: Translated version
ties (but in general, one may require the exact definitions
for these functions).
Example 13:
mget function from Figure 2.
rithm constructs bitvector types for the variables and the
translate procedure constructs C structures corresponding
to these types, where each field is an unsigned 32 bit en
try. The field BOT n refers to a zero block in the type. For
example, the structure struct
type ?k,20??b,11??s,1? and struct
012?k,20?. The assignments are translated to structure as
signments. For array indices, we convert the bitvector struc
ture to the corresponding integer by a systemdefined func
tion (to allow array indexing by the structures). (We have
omitted the assertions for arithmetic operations.)
Figure 10 shows the translation of the
The type inference algo
b5 encodes the bitvector
b1 encodes the type
2
5.EXPERIMENTS
5.1 Implementation and Performance
We have implemented the type inference algorithm with
graph based data structures for C. We extended the algo
rithm of the previous sections to handle all the constructs of
C, including pointers and fields. Similar to arrays, we pro
vide a single type for a field. In addition, we handle bitwise
operations with variable values (rather than constants). We
implement a value set algorithm to handle the simple cases
when a small set of constant values can flow into a variable.
For others, we extend our translation algorithm to handle
the cases outside the type system. We introduce conversion
functions from integers to records and back, and cast val
ues from one type to the other when the type system or the
translation fails to handle a particular case.
We ran the tool on two sets of experiments. The first set,
reported below, demonstrates that the constraint generation
and type inference is efficient and scalable: in all cases, type
inference took only a few seconds. The main objective of
the experiments was in program understanding: we wanted
to see if bitvector types produced data layout that was in
formally specified in the comments of the code, and to see
how the types flow between program variables. The second
set of experiments, described in the next section, show the
applicability of the type inference to software verification.
General benchmarks. We ran the type inference on a set
of Linux drivers as well as a set of programs from the Medi
abench benchmark [15]. In each case, the inference took less
than 5s. In several Mediabench benchmarks, bitwise oper
ations are used for computations such as FFT. Such usage
does not conform to our type system which breaks the vari
ables involved into individual bits. However, for variables
where part of the bits store data and part of the bits store
permissions, the type system finds the appropriate type.
pmap. We applied our bitvector type inference algorithm
to the code that sets up the page table in JOS, a small OS
kernel written for education purposes [12]. The code was
about 800 lines of C, with bitvector operations similar to the
example code in Figure 2. The constraint generation took
0.03s and the type inference took 0.21s on a 1GHz Linux
machine with 1GB RAM. There were 380 type constraints.
Of the 257 lvalues, 97 had nontrivial bitvector types (the
others were not involved in bitwise operations, and hence
had types of the form ?p,32? indicating that the 32 bits
were not broken into subwords by the program). The type
inference correctly identifies a physical address to have the
type ?a,20??b,11??c,1?. Further, it identifies the variable
npage that tracks the entries into the page table to have
the type 012?a,20? thus showing that only the last 20 bits
are relevant, and moreover, the top 20 bits from a virtual
address flows into npage (since both have the type name a).
Further, the type of the variable cr3 (standing for the %cr3
processor register) is also identified to be ?a,20??b,11??c,1?.
This conformed to the hardware layer specification as well
as the informal comments in the code.
5.2Application to Verification
We ran the bitvector typing on two case studies for soft
ware verification: an implementation of the Mondrian mem
ory protection [22] and a Linux device driver. In each case,
we ran the translation algorithm on the original code (an
notated with assertions), and then ran the software model
checker Blast on the output. We describe each case study
below.
Mondrian Memory Protection. Mondrian [22] is a fine
grained protection scheme that allows multiple protection
domains to share memory. We ran the bitvector inference al
gorithm on an implementation of Mondrian annotated with
10 assertions provided to us by the author, E. Witchel. Mon
drian extensively uses bitpacked structures to keep track
of memory structures and permission bits. Since many of
the assertions involved reasoning about bitvectors, Blast was
able to verify only 3 of the assertions on the original code.
The preprocessed code had 2687 lines. There were 775 con
straints, and type inference took about 0.8s. The translated
code introduced 18 different bitvector structures to repre
sent the bitvector types inferred.
Table 1 summarizes the number of predicates found by
Blast in each run, the number of predicates involving at
least one bitvector field, as well as the run time for the veri
fication. On the translated version, Blast was able to verify
Page 12
Assertion
1
2
3
4
5
6
7
8
9
10
Predicates
6
14
14
25
32
64
32
41
27
33
Bitvector
0
2
1
5
0
5
11
24
10
0
Time (s)
9
9
8
13
26
29
106
110
55
21
FP
array
toint
toint
array
Table 1: Mondrian experiments. Predicates is the
total number of predicates found by Blast, Bit
vector is the number of predicates that directly in
volved a bitvector field. FP is the reason Blast had
a false positive.
6 of the 10 assertions and produced 4 false positives. We
manually inspected the remaining assertions. Two of these
involved data flow from (multilevel) arrays. Proving these
assertions require reasoning about array data structures and
more sophisticated alias analyses than Blast is currently ca
pable of doing. Two other false positives involved loss of
information because of a conversion to integers from bitvec
tors and back (these conversion functions were treated as
uninterpreted functions by the analysis). We inspected the
code manually, and found instances where bitvector opera
tions were performed with variables, for example:
idx = 1 << mmpt>tab[lev];
However, the values that can flow into mmpt>tab[lev] are
either 3 or 4, so we rewrote the code into a switch based
on the value in the table. With this rewriting, we ran Blast
again, and one of the false positives (assertion 6) went away
(with 27 predicates, and 4 with bitvectors). For the second,
we found a new false positive that again required reasoning
about arrays.
scull Linux Driver. We also ran our procedure on the
driver scull [4]. The Linux kernel uses a type kdev t to
hold major and minor device numbers. The type kdev t is
a 32bit quantity with 12 bits set aside for the major number
and 20 for the minor number. The major and minor numbers
are extracted using macros in the kernel that expand to
int type = (inode>i_rdev >> 20); // major
int num = (inode>i_rdev & 0xFFFFF) ; // minor
In a previous verification of this driver using Blast [11], these
bitvector operations were translated away by hand.
type inference automatically identifies the type of the field
as ?a,12??b,20?, and the translation procedure replaced the
type kdev t with a structure with two fields, a and b, and
simplified the above code to
The
int type = (inode>i_rdev).a;
int num= (inode>i_rdev).b;
This translation eliminated the bitvector operations, and
Blast could check a set of five properties relating to the
correctness of the driver.
Acknowledgments.We would like to thank Emmett
Witchel for providing us with the Mondrian Memory Pro
tection code and the assertions therein, and RuGang Xu for
attempting to use Blast to verify the properties using the
bitreduction approach. We thank the anonymous review
ers for pointing out the closely related papers [19, 13]. This
research was supported in part by the NSF grants CCF
0427202, CNS0541606, and CCF0546170.
6.
[1] T. Ball and S.K. Rajamani. The SLAM project: debugging
system software via static analysis. In POPL 02: Principles of
Programming Languages, pages 1–3. ACM, 2002.
[2] C. Barrett, D. Dill, and J. Levitt. A decision procedure for
bitvector arithmetic. In DAC 98: Design Automation
Conference, pages 522–527, 1998.
[3] E.M. Clarke, D. Kroening, and F. Lerda. A tool for checking
ANSIC programs. In TACAS 04: Tools and Algorithms for
the construction and analysis of systems, LNCS 2988, pages
168–176. Springer, 2004.
[4] J. Corbet, G. KroahHartman, and A. Rubini. Linux device
drivers, 3rd edition. O’Reilly, 2005.
[5] M. Das, S. Lerner, and M. Seigle. ESP: Pathsensitive program
verification in polynomial time. In PLDI 02: Programming
Language Design and Implementation, pages 57–68. ACM,
2002.
[6] D. Detlefs, G. Nelson, and J.B. Saxe. Simplify: a theorem
prover for program checking. J. ACM, 52(3):365–473, 2005.
[7] C. Flanagan. Hybrid type checking. In POPL 06: Principles of
Programming Languages, pages 245–256. ACM Press, 2006.
[8] J.S. Foster, T. Terauchi, and A. Aiken. Flowsensitive type
qualifiers. In PLDI 02: Programming Language Design and
Implementation, pages 1–12. ACM, 2002.
[9] R. Gupta, E. Mehofer, and Y. Zhang. A representation for bit
section based analysis and optimization. In CC 02: Compiler
Construction, LNCS 2304, pages 62–77. Springer, 2002.
[10] T.A. Henzinger, R. Jhala, R. Majumdar, and G. Sutre. Lazy
abstraction. In POPL 02: Principles of Programming
Languages, pages 58–70. ACM, 2002.
[11] R. Jhala and K.L. McMillan. Interpolantbased transition
relation approximation. In CAV 05: ComputerAided
Verification, LNCS 3576, pages 39–51. Springer, 2005.
[12] JOS. Jos: An operating system kernel.
http://pdos.csail.mit.edu/6.828/2005/overview.html.
[13] R. Komondoor, G. Ramalingam, J. Field, and S. Chandra.
Dependent types for program understanding. In TACAS 05:
Tools and Algorithms for the Construction and Analysis of
Systems, LNCS 3440, pages 157–173. Springer, 2005.
[14] Daniel Kroening and Natasha Sharygina. Approximating
predicate images for bitvector logic. In TACAS, pages
242–256, 2006.
[15] C. Lee, M. Potkonjak, and W.H. MangioneSmith. Mediabench:
A tool for evaluating and synthesizing multimedia and
communicatons systems. In MICRO 97: IEEE/ACM
Symposium on Microarchitecture, pages 330–335. IEEE, 1997.
[16] B. Li and R. Gupta. Bit section instruction set extension of
arm for embedded applications. In CASES 02, pages 69–78.
ACM, 2002.
[17] M.O. M¨ oller and H. Ruess. Solving bitvector equations. In
FMCAD 98: Formal Methods in ComputerAided Design,
LNCS 1522, pages 36–48. Springer, 1998.
[18] R. O’Callahan and D. Jackson. Lackwit: A program
understanding tool based on type inference. In ICSE 97:
International Conference on Software Engineering, pages
338–348. ACM, 1997.
[19] G. Ramalingam, J. Field, and F. Tip. Aggregate structure
identification and its application to program analysis. In POPL
99: Principles of Programming Languages, pages 119–132.
ACM, 1999.
[20] A. Stump, C.W. Barrett, and D.L. Dill. Cvc: A cooperating
validity checker. In CAV 02: ComputerAided Verification,
LNCS 2404, pages 500–504. Springer, 2002.
[21] S. Tallam and R. Gupta. Bitwidth aware global register
allocation. In POPL 03: Principles of Programming
Languages, pages 85–96. ACM, 2003.
[22] E. Witchel, J. Cates, and K. Asanovi´ c. Mondrian memory
protection. In ASPLOS 02. ACM, 2002.
[23] Y. Xie and A. Aiken. Scalable error detection using boolean
satisfiability. In POPL 05: Principles of Programming
Languages, pages 351–363. ACM, 2005.
REFERENCES