Intro to Region Decomposition
Core Concepts
- Decomposition Goal: To break down a function
finto a set of regions[ {cs_0, inv_0}, {cs_1, inv_1}, ..., {cs_m, inv_m} ]such that the function's body is equivalent toif cs_0 then inv_0 else if cs_1 then inv_1 ... else inv_mover the relevant input domain. - Regions: Each region consists of:
- Constraints (
cs): A list of Boolean terms representing conditions on the function's inputs. Logically, these constraints within a single region form a conjunction (AND). The constraint language avoids complex control flow likeITEormatchexpressions. - Invariant (
inv): The simplified symbolic expression representing the function's output when the region's constraints are satisfied.
- Constraints (
- Disjointness & Coverage: For a decomposition generated directly by
top(without~assuming), the regions are typically disjoint (an input satisfies the constraints of at most one region), and together they cover the function's entire input domain. However, subsequent manipulation using composition operators (like<<,~|, etc.) might result in overlapping regions. Furthermore, using the~assumingoption restricts the analysis, meaning the regions only cover the portion of the input domain where the assumption holds. The decomposition aims to represent the covered domain's behavior in a DNF-like structure based on its input conditions. - Feasibility & Models: The constraints of each region can be checked for satisfiability. If feasible, a concrete model (an example input satisfying the constraints) can often be extracted.
- This concrete model can be further transformed into a test case using
codelogicianCLI.
- This concrete model can be further transformed into a test case using
Basic Usage
The top function from the Decomp module is the primary way to initiate decomposition. It starts the process and accepts several optional configuration arguments:
Type signature of top:
val top :
?assuming:identifier ->
?basis:identifier list ->
?rule_specs:identifier list ->
?prune:bool ->
?ctx_simp:bool ->
unit ->
m-
Basic Invocation:
let f x = if x = 1 || x = 2 then x + 1 else x - 1 [@@decomp top ()] (* Produces regions based on conditions x=1, x=2, etc. *) -
~assuming: Decomposes the function under an additional assumption (a precondition). Takes an identifier representing a Boolean function.let asm x = x > 1 let g x = ... [@@decomp top ~assuming:[%id asm] ()] (* Decomposes g only for inputs where asm(x) is true *)Common Error: the additional assumption must have the exact same signature as the function being decomposed. For example:
(* WRONG - different parameters *)
let valid x = x > 0
let func x y = x + y
[@@decomp top ~assuming:[%id valid] ()]
(* CORRECT *)
let valid x y = x > 0 && y > 0
let func x y = x + y
[@@decomp top ~assuming:[%id valid] ()]-
~basis: Specifies a list of function identifiers that should not be expanded or interpreted during decomposition. They remain symbolic in the resulting regions.let g x = if f x > x then -1 else 1 [@@decomp top ~basis:[ [%id f] ] ()] (* Decomposes g, but leaves calls to f as 'f x' *)Common Error: the correct syntax for multiple identifiers is
[[%id f1]; [%id f2]], not[%id f1; %id f2]. -
~ctx_simp: Enables/disables contextual simplification during decomposition (e.g., value propagation, rewritingx <> yasnot (x = y)).let i x y = ... [@@decomp top ~ctx_simp:true ()] -
~prune: Boolean flag to attempt full pruning of infeasible regions and model extraction for the feasible regions during the initialtopstep. -
~rule_specs: Specifies identifiers of rule specs to be used for rewriting or forward-chaining within the decomposition process.
Common Errors and Anti-Patterns
Mismatched ~assuming signature:
(* WRONG - different parameter count *)
let valid x = x > 0
let func x y = x + y
[@@decomp top ~assuming:[%id valid] ()]
(* CORRECT *)
let valid x y = x > 0 && y > 0
let func x y = x + y
[@@decomp top ~assuming:[%id valid] ()]Exponentially large number of regions: Sometimes the algorithmic complexity of a decomposed function can lead to an exponential number of regions, especially if the function has many branches or nested conditions.
- Double-check what is the desired target of region decomposition. If the goal is only a subset of the function's behavior, consider using
~basisor~assumingto restrict the analysis.- Quick fix 1: Use
~basisto avoid expanding certain functions. - Quick fix 2: Use
~assumingto restrict the analysis to a smaller input domain.
- Quick fix 1: Use
- Sometimes the type definition of a function is too loose / general, resulting in overly broad regions. For example, using
stringorintinstead of variant types to represent a finite set of values is considered an anti-pattern.