Skip to content

Commit 5e857cc

Browse files
committed
feat: ignore unreachable branch to improve branch coverage precision
In AS test, we definitely do not want to touch unreachable branch. And since unreachable will fail the execution and result output, it does not make sure to statistic unreachable branch coverage This PR remove the basic blocks which must unreachable.
1 parent 92479b1 commit 5e857cc

File tree

6 files changed

+148
-19
lines changed

6 files changed

+148
-19
lines changed

.prettierignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@
55
/transform/*.mjs
66
/tests/ts/fixture
77
/third_party
8-
/tests/cpp/lit/**/*.json
98
/.cache
109
/.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)