Skip to content

Commit 4e59241

Browse files
authored
feat: ignore unreachable branch to improve branch coverage precision (#19)
1 parent 92479b1 commit 4e59241

File tree

6 files changed

+149
-19
lines changed

6 files changed

+149
-19
lines changed

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
/transform/*.mjs
66
/tests/ts/fixture
77
/third_party
8-
/tests/cpp/lit/**/*.json
8+
/tests/cpp/lit/expectInstrument**/*.json
99
/.cache
1010
/.github/CODEOWNERS

instrumentation/BasicBlockWalker.cpp

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
#include "BasicBlockWalker.hpp"
2+
#include <algorithm>
23
#include <cfg/cfg-traversal.h>
4+
#include <iostream>
5+
#include <memory>
6+
#include <set>
7+
#include <utility>
8+
#include <wasm.h>
39
#include "ir/branch-utils.h"
410

511
namespace wasmInstrumentation {
@@ -27,30 +33,72 @@ void BasicBlockWalker::visitExpression(wasm::Expression *curr) noexcept {
2733
}
2834
}
2935

30-
void BasicBlockWalker::unlinkEmptyBlock() noexcept {
31-
const auto lambda = [](std::unique_ptr<BasicBlock> &block) {
32-
if (block->contents.exprs.empty() && block->out.size() == 1) {
33-
const auto outBlock = block->out[0];
34-
outBlock->in.erase(std::find(outBlock->in.begin(), outBlock->in.end(), block.get()));
35-
for (auto &inBlock : block->in) {
36-
inBlock->out.erase(std::find(inBlock->out.begin(), inBlock->out.end(), block.get()));
37-
inBlock->out.push_back(outBlock);
38-
outBlock->in.push_back(inBlock);
36+
static bool
37+
isBasicBlockContainUnreachable(BasicBlockWalker::BasicBlock &block,
38+
std::set<BasicBlockWalker::BasicBlock *> unreachableBlocks) {
39+
return (!block.contents.exprs.empty() &&
40+
std::any_of(block.contents.exprs.begin(), block.contents.exprs.end(),
41+
[](wasm::Expression *expr) {
42+
return expr->is<wasm::Unreachable>();
43+
})) ||
44+
(!block.in.empty() &&
45+
std::all_of(block.in.begin(), block.in.end(),
46+
[&unreachableBlocks](BasicBlockWalker::BasicBlock *inBlock) {
47+
return unreachableBlocks.find(inBlock) != unreachableBlocks.end();
48+
}));
49+
};
50+
51+
static void removeDuplicates(std::vector<BasicBlockWalker::BasicBlock *> &list) {
52+
std::sort(list.begin(), list.end());
53+
list.erase(std::unique(list.begin(), list.end()), list.end());
54+
}
55+
56+
void BasicBlockWalker::cleanBlock() noexcept {
57+
bool isModified = true;
58+
std::set<BasicBlock *> unreachableBlocks{};
59+
while (isModified) {
60+
isModified = false;
61+
for (auto &block : basicBlocks) {
62+
if (isBasicBlockContainUnreachable(*block, unreachableBlocks)) {
63+
isModified |= unreachableBlocks.insert(block.get()).second;
3964
}
40-
block->in.clear();
41-
block->out.clear();
42-
return true;
4365
}
44-
return false;
45-
};
46-
basicBlocks.erase(std::remove_if(basicBlocks.begin(), basicBlocks.end(), lambda),
47-
basicBlocks.end());
66+
}
67+
std::set<BasicBlock *> emptyBlocks{};
68+
for (auto &block : basicBlocks) {
69+
if (block->contents.exprs.empty() && block->out.size() == 1) {
70+
emptyBlocks.insert(block.get());
71+
}
72+
}
73+
74+
std::set<BasicBlock *> targetCleanBlocks{};
75+
targetCleanBlocks.insert(unreachableBlocks.begin(), unreachableBlocks.end());
76+
targetCleanBlocks.insert(emptyBlocks.begin(), emptyBlocks.end());
77+
78+
for (auto &block : targetCleanBlocks) {
79+
for (auto &outBlock : block->out) {
80+
outBlock->in.erase(std::find(outBlock->in.begin(), outBlock->in.end(), block));
81+
outBlock->in.insert(outBlock->in.end(), block->in.begin(), block->in.end());
82+
removeDuplicates(outBlock->in);
83+
}
84+
for (auto &inBlock : block->in) {
85+
inBlock->out.erase(std::find(inBlock->out.begin(), inBlock->out.end(), block));
86+
inBlock->out.insert(inBlock->out.end(), block->out.begin(), block->out.end());
87+
removeDuplicates(inBlock->out);
88+
}
89+
block->in.clear();
90+
block->out.clear();
91+
basicBlocks.erase(std::find_if(basicBlocks.begin(), basicBlocks.end(),
92+
[&block](std::unique_ptr<BasicBlock> const &b) -> bool {
93+
return b.get() == block;
94+
}));
95+
}
4896
}
4997

5098
void BasicBlockWalker::doWalkFunction(wasm::Function *const func) noexcept {
5199
wasm::CFGWalker<BasicBlockWalker, wasm::UnifiedExpressionVisitor<BasicBlockWalker>,
52100
BasicBlockInfo>::doWalkFunction(func);
53-
unlinkEmptyBlock();
101+
cleanBlock();
54102
// LCOV_EXCL_START
55103
if (basicBlocks.size() > UINT32_MAX) {
56104
std::cerr << "Error: BasicBlocks length exceeds UINT32_MAX\n";

instrumentation/BasicBlockWalker.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ class BasicBlockWalker final
167167
///
168168
/// @brief remove empty block that do not belong to any branch
169169
///
170-
void unlinkEmptyBlock() noexcept;
170+
void cleanBlock() noexcept;
171171
};
172172
} // namespace wasmInstrumentation
173173

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
(module
2+
(type $0 (func (param i32 i32 i32 i32)))
3+
(type $1 (func (param i32)))
4+
(type $2 (func))
5+
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
6+
(global $~lib/memory/__data_end i32 (i32.const 76))
7+
(global $~lib/memory/__stack_pointer (mut i32) (i32.const 32844))
8+
(global $~lib/memory/__heap_base i32 (i32.const 32844))
9+
(memory $0 1)
10+
(data $0 (i32.const 12) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\"\00\00\00a\00s\00s\00e\00m\00b\00l\00y\00/\00i\00n\00d\00e\00x\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00")
11+
(table $0 1 1 funcref)
12+
(elem $0 (i32.const 1))
13+
(export "main" (func $assembly/index/main))
14+
(export "memory" (memory $0))
15+
(func $assembly/index/f (param $a i32)
16+
;;@ assembly/index.ts:5:2
17+
(if
18+
;;@
19+
(i32.eqz
20+
;;@ assembly/index.ts:5:9
21+
(i32.ge_s
22+
(local.get $a)
23+
;;@ assembly/index.ts:5:14
24+
(i32.const 10)
25+
)
26+
)
27+
(then
28+
;;@
29+
(call $~lib/builtins/abort
30+
(i32.const 0)
31+
(i32.const 32)
32+
(i32.const 5)
33+
(i32.const 3)
34+
)
35+
;;@
36+
(unreachable)
37+
)
38+
)
39+
)
40+
(func $assembly/index/main
41+
;;@ assembly/index.ts:2:2
42+
(call $assembly/index/f
43+
;;@ assembly/index.ts:2:4
44+
(i32.const 10)
45+
)
46+
;;@ assembly/index.ts:2:2
47+
)
48+
;; custom section "sourceMappingURL", size 17
49+
)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"debugFiles": ["assembly/index.ts"],
3+
"debugInfos": {
4+
"assembly/index/f": {
5+
"branchInfo": [],
6+
"index": 0,
7+
"lineInfo": [
8+
[
9+
[0, 5, 2],
10+
[0, 5, 9],
11+
[0, 5, 14]
12+
],
13+
[]
14+
]
15+
},
16+
"assembly/index/main": {
17+
"branchInfo": [],
18+
"index": 1,
19+
"lineInfo": [
20+
[
21+
[0, 2, 2],
22+
[0, 2, 4]
23+
]
24+
]
25+
}
26+
}
27+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
make directly call to function index=1
2+
make directly call to function index=0
3+
basic block entry trace to: function=0, basic block=0
4+
basic block entry trace to: function=0, basic block=1
5+
basic block entry trace to: function=1, basic block=0
6+
exit from function call index=0

0 commit comments

Comments
 (0)