Skip to content

Commit ae0ca75

Browse files
rocallahanjix
authored andcommitted
Use a pool of ABC processes.
Doing ABC runs in parallel can actually make things slower when every ABC run requires spawning an ABC subprocess --- especially when using popen(), which on glibc does not use vfork(). What seems to happen is that constant fork()ing keeps making the main process data pages copy-on-write, so the main process code that is setting up each ABC call takes a lot of minor page-faults, slowing it down. The solution is pretty straightforward although a little tricky to implement. We just reuse ABC subprocesses. Instead of passing the ABC script name on the command line, we spawn an ABC REPL and pipe a command into it to source the script. When that's done we echo an `ABC_DONE` token instead of exiting. Yosys then puts the ABC process onto a stack which we can pull from the next time we do an ABC run. For one of our large designs, this is an additional 5x speedup of the primary AbcPass. It does 5155 ABC runs, all very small; runtime of the AbcPass goes from 760s to 149s (not very scientific benchmarking but the effect size is large).
1 parent 27462da commit ae0ca75

File tree

2 files changed

+221
-13
lines changed

2 files changed

+221
-13
lines changed

kernel/threading.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,33 @@ class ThreadPool
154154
#endif
155155
};
156156

157+
template <class T>
158+
class ConcurrentStack
159+
{
160+
public:
161+
void push_back(T &&t) {
162+
#ifdef YOSYS_ENABLE_THREADS
163+
std::lock_guard<std::mutex> lock(mutex);
164+
#endif
165+
contents.push_back(std::move(t));
166+
}
167+
std::optional<T> try_pop_back() {
168+
#ifdef YOSYS_ENABLE_THREADS
169+
std::lock_guard<std::mutex> lock(mutex);
170+
#endif
171+
if (contents.empty())
172+
return std::nullopt;
173+
T result = std::move(contents.back());
174+
contents.pop_back();
175+
return result;
176+
}
177+
private:
178+
#ifdef YOSYS_ENABLE_THREADS
179+
std::mutex mutex;
180+
#endif
181+
std::vector<T> contents;
182+
};
183+
157184
YOSYS_NAMESPACE_END
158185

159186
#endif // YOSYS_THREADING_H

passes/techmap/abc.cc

Lines changed: 194 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
#include <memory>
6060
#include <vector>
6161

62+
#ifdef __linux__
63+
# include <fcntl.h>
64+
# include <spawn.h>
65+
# include <sys/wait.h>
66+
#endif
6267
#ifndef _WIN32
6368
# include <unistd.h>
6469
# include <dirent.h>
@@ -153,6 +158,121 @@ struct AbcSigVal {
153158
}
154159
};
155160

161+
#if defined(__linux__) && !defined(YOSYS_DISABLE_SPAWN)
162+
struct AbcProcess
163+
{
164+
pid_t pid;
165+
int to_child_pipe;
166+
int from_child_pipe;
167+
168+
AbcProcess() : pid(0), to_child_pipe(-1), from_child_pipe(-1) {}
169+
AbcProcess(AbcProcess &&other) {
170+
pid = other.pid;
171+
to_child_pipe = other.to_child_pipe;
172+
from_child_pipe = other.from_child_pipe;
173+
other.pid = 0;
174+
other.to_child_pipe = other.from_child_pipe = -1;
175+
}
176+
AbcProcess &operator=(AbcProcess &&other) {
177+
if (this != &other) {
178+
pid = other.pid;
179+
to_child_pipe = other.to_child_pipe;
180+
from_child_pipe = other.from_child_pipe;
181+
other.pid = 0;
182+
other.to_child_pipe = other.from_child_pipe = -1;
183+
}
184+
return *this;
185+
}
186+
~AbcProcess() {
187+
if (pid == 0)
188+
return;
189+
if (to_child_pipe >= 0)
190+
close(to_child_pipe);
191+
int status;
192+
int ret = waitpid(pid, &status, 0);
193+
if (ret != pid) {
194+
log_error("waitpid(%d) failed", pid);
195+
}
196+
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
197+
log_error("ABC failed with status %X", status);
198+
}
199+
if (from_child_pipe >= 0)
200+
close(from_child_pipe);
201+
}
202+
};
203+
204+
std::optional<AbcProcess> spawn_abc(const char* abc_exe, DeferredLogs &logs) {
205+
// Open pipes O_CLOEXEC so we don't leak any of the fds into racing
206+
// fork()s.
207+
int to_child_pipe[2];
208+
if (pipe2(to_child_pipe, O_CLOEXEC) != 0) {
209+
logs.log_error("pipe failed");
210+
return std::nullopt;
211+
}
212+
int from_child_pipe[2];
213+
if (pipe2(from_child_pipe, O_CLOEXEC) != 0) {
214+
logs.log_error("pipe failed");
215+
return std::nullopt;
216+
}
217+
218+
AbcProcess result;
219+
result.to_child_pipe = to_child_pipe[1];
220+
result.from_child_pipe = from_child_pipe[0];
221+
// Allow the child side of the pipes to be inherited.
222+
fcntl(to_child_pipe[0], F_SETFD, 0);
223+
fcntl(from_child_pipe[1], F_SETFD, 0);
224+
225+
posix_spawn_file_actions_t file_actions;
226+
if (posix_spawn_file_actions_init(&file_actions) != 0) {
227+
logs.log_error("posix_spawn_file_actions_init failed");
228+
return std::nullopt;
229+
}
230+
231+
if (posix_spawn_file_actions_addclose(&file_actions, to_child_pipe[1]) != 0) {
232+
logs.log_error("posix_spawn_file_actions_addclose failed");
233+
return std::nullopt;
234+
}
235+
if (posix_spawn_file_actions_addclose(&file_actions, from_child_pipe[0]) != 0) {
236+
logs.log_error("posix_spawn_file_actions_addclose failed");
237+
return std::nullopt;
238+
}
239+
if (posix_spawn_file_actions_adddup2(&file_actions, to_child_pipe[0], STDIN_FILENO) != 0) {
240+
logs.log_error("posix_spawn_file_actions_adddup2 failed");
241+
return std::nullopt;
242+
}
243+
if (posix_spawn_file_actions_adddup2(&file_actions, from_child_pipe[1], STDOUT_FILENO) != 0) {
244+
logs.log_error("posix_spawn_file_actions_adddup2 failed");
245+
return std::nullopt;
246+
}
247+
if (posix_spawn_file_actions_adddup2(&file_actions, from_child_pipe[1], STDERR_FILENO) != 0) {
248+
logs.log_error("posix_spawn_file_actions_adddup2 failed");
249+
return std::nullopt;
250+
}
251+
if (posix_spawn_file_actions_addclose(&file_actions, to_child_pipe[0]) != 0) {
252+
logs.log_error("posix_spawn_file_actions_addclose failed");
253+
return std::nullopt;
254+
}
255+
if (posix_spawn_file_actions_addclose(&file_actions, from_child_pipe[1]) != 0) {
256+
logs.log_error("posix_spawn_file_actions_addclose failed");
257+
return std::nullopt;
258+
}
259+
260+
char arg1[] = "-s";
261+
char* argv[] = { strdup(abc_exe), arg1, nullptr };
262+
if (0 != posix_spawn(&result.pid, abc_exe, &file_actions, nullptr, argv, environ)) {
263+
logs.log_error("posix_spawn %s failed", abc_exe);
264+
return std::nullopt;
265+
}
266+
free(argv[0]);
267+
posix_spawn_file_actions_destroy(&file_actions);
268+
close(to_child_pipe[0]);
269+
close(from_child_pipe[1]);
270+
return result;
271+
}
272+
#else
273+
struct AbcProcess {};
274+
#endif
275+
156276
using AbcSigMap = SigValMap<AbcSigVal>;
157277

158278
// Used by off-main-threads. Contains no direct or indirect access to RTLIL.
@@ -167,7 +287,7 @@ struct RunAbcState {
167287
dict<int, std::string> pi_map, po_map;
168288

169289
RunAbcState(const AbcConfig &config) : config(config) {}
170-
void run();
290+
void run(ConcurrentStack<AbcProcess> &process_pool);
171291
};
172292

173293
struct AbcModuleState {
@@ -1010,7 +1130,42 @@ void AbcModuleState::prepare_module(RTLIL::Design *design, RTLIL::Module *module
10101130
handle_loops(assign_map, module);
10111131
}
10121132

1013-
void RunAbcState::run()
1133+
bool read_until_abc_done(abc_output_filter &filt, int fd, DeferredLogs &logs) {
1134+
std::string line;
1135+
char buf[1024];
1136+
while (true) {
1137+
int ret = read(fd, buf, sizeof(buf) - 1);
1138+
if (ret < 0) {
1139+
logs.log_error("Failed to read from ABC, errno=%d", errno);
1140+
return false;
1141+
}
1142+
if (ret == 0) {
1143+
logs.log_error("ABC exited prematurely");
1144+
return false;
1145+
}
1146+
char *start = buf;
1147+
char *end = buf + ret;
1148+
while (start < end) {
1149+
char *p = static_cast<char*>(memchr(start, '\n', end - start));
1150+
if (p == nullptr) {
1151+
break;
1152+
}
1153+
line.append(start, p + 1 - start);
1154+
// ABC seems to actually print "ABC_DONE \n", but we probably shouldn't
1155+
// rely on that extra space being output.
1156+
if (line.substr(0, 8) == "ABC_DONE") {
1157+
// Ignore any leftover output, there should only be a prompt perhaps
1158+
return true;
1159+
}
1160+
filt.next_line(line);
1161+
line.clear();
1162+
start = p + 1;
1163+
}
1164+
line.append(start, end - start);
1165+
}
1166+
}
1167+
1168+
void RunAbcState::run(ConcurrentStack<AbcProcess> &process_pool)
10141169
{
10151170
std::string buffer = stringf("%s/input.blif", tempdir_name);
10161171
FILE *f = fopen(buffer.c_str(), "wt");
@@ -1137,14 +1292,12 @@ void RunAbcState::run()
11371292
count_gates, GetSize(signal_list), count_input, count_output);
11381293
if (count_output > 0)
11391294
{
1140-
buffer = stringf("\"%s\" -s -f %s/abc.script 2>&1", config.exe_file, tempdir_name);
1141-
logs.log("Running ABC command: %s\n", replace_tempdir(buffer, tempdir_name, config.show_tempdir));
1295+
std::string tmp_script_name = stringf("%s/abc.script", tempdir_name);
1296+
logs.log("Running ABC script: %s\n", replace_tempdir(tmp_script_name, tempdir_name, config.show_tempdir));
11421297

11431298
errno = 0;
1144-
#ifndef YOSYS_LINK_ABC
11451299
abc_output_filter filt(*this, tempdir_name, config.show_tempdir);
1146-
int ret = run_command(buffer, std::bind(&abc_output_filter::next_line, filt, std::placeholders::_1));
1147-
#else
1300+
#ifdef YOSYS_LINK_ABC
11481301
string temp_stdouterr_name = stringf("%s/stdouterr.txt", tempdir_name);
11491302
FILE *temp_stdouterr_w = fopen(temp_stdouterr_name.c_str(), "w");
11501303
if (temp_stdouterr_w == NULL)
@@ -1165,7 +1318,6 @@ void RunAbcState::run()
11651318
fclose(temp_stdouterr_w);
11661319
// These needs to be mutable, supposedly due to getopt
11671320
char *abc_argv[5];
1168-
string tmp_script_name = stringf("%s/abc.script", tempdir_name);
11691321
abc_argv[0] = strdup(config.exe_file.c_str());
11701322
abc_argv[1] = strdup("-s");
11711323
abc_argv[2] = strdup("-f");
@@ -1183,13 +1335,40 @@ void RunAbcState::run()
11831335
fclose(old_stdout);
11841336
fclose(old_stderr);
11851337
std::ifstream temp_stdouterr_r(temp_stdouterr_name);
1186-
abc_output_filter filt(*this, tempdir_name, config.show_tempdir);
11871338
for (std::string line; std::getline(temp_stdouterr_r, line); )
11881339
filt.next_line(line + "\n");
11891340
temp_stdouterr_r.close();
1341+
#elif defined(__linux__) && !defined(YOSYS_DISABLE_SPAWN)
1342+
AbcProcess process;
1343+
if (std::optional<AbcProcess> process_opt = process_pool.try_pop_back()) {
1344+
process = std::move(process_opt.value());
1345+
} else if (std::optional<AbcProcess> process_opt = spawn_abc(config.exe_file.c_str(), logs)) {
1346+
process = std::move(process_opt.value());
1347+
} else {
1348+
return;
1349+
}
1350+
std::string cmd = stringf(
1351+
// This makes ABC switch stdout to line buffering, which we need
1352+
// to see our ABC_DONE message.
1353+
"set abcout /dev/stdout\n"
1354+
"empty\n"
1355+
"source %s\n"
1356+
"echo \"ABC_DONE\"\n", tmp_script_name);
1357+
int ret = write(process.to_child_pipe, cmd.c_str(), cmd.size());
1358+
if (ret != static_cast<int>(cmd.size())) {
1359+
logs.log_error("write failed");
1360+
return;
1361+
}
1362+
ret = read_until_abc_done(filt, process.from_child_pipe, logs) ? 0 : 1;
1363+
if (ret == 0) {
1364+
process_pool.push_back(std::move(process));
1365+
}
1366+
#else
1367+
std::string cmd = stringf("\"%s\" -s -f %s/abc.script 2>&1", config.exe_file.c_str(), tempdir_name.c_str());
1368+
int ret = run_command(cmd, std::bind(&abc_output_filter::next_line, filt, std::placeholders::_1));
11901369
#endif
11911370
if (ret != 0) {
1192-
logs.log_error("ABC: execution of command \"%s\" failed: return code %d (errno=%d).\n", buffer, ret, errno);
1371+
logs.log_error("ABC: execution of script \"%s\" failed: return code %d (errno=%d).\n", tmp_script_name, ret, errno);
11931372
return;
11941373
}
11951374
did_run = true;
@@ -2205,7 +2384,8 @@ struct AbcPass : public Pass {
22052384

22062385
AbcModuleState state(config, initvals);
22072386
state.prepare_module(design, mod, assign_map, cells, dff_mode, clk_str);
2208-
state.run_abc.run();
2387+
ConcurrentStack<AbcProcess> process_pool;
2388+
state.run_abc.run(process_pool);
22092389
state.extract(assign_map, design, mod);
22102390
continue;
22112391
}
@@ -2379,11 +2559,12 @@ struct AbcPass : public Pass {
23792559
ConcurrentQueue<std::unique_ptr<AbcModuleState>> work_queue(num_worker_threads);
23802560
ConcurrentQueue<std::unique_ptr<AbcModuleState>> work_finished_queue;
23812561
int work_finished_count = 0;
2562+
ConcurrentStack<AbcProcess> process_pool;
23822563
ThreadPool worker_threads(num_worker_threads, [&](int){
23832564
while (std::optional<std::unique_ptr<AbcModuleState>> work =
23842565
work_queue.pop_front()) {
23852566
// Only the `run_abc` component is safe to touch here!
2386-
(*work)->run_abc.run();
2567+
(*work)->run_abc.run(process_pool);
23872568
work_finished_queue.push_back(std::move(*work));
23882569
}
23892570
});
@@ -2409,7 +2590,7 @@ struct AbcPass : public Pass {
24092590
work_queue.push_back(std::move(state));
24102591
} else {
24112592
// Just run everything on the main thread.
2412-
state->run_abc.run();
2593+
state->run_abc.run(process_pool);
24132594
work_finished_queue.push_back(std::move(state));
24142595
}
24152596
}

0 commit comments

Comments
 (0)