Generated on for Gecode by doxygen 1.15.0
blackbox-backend.cpp
Go to the documentation of this file.
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2/*
3 * Main authors:
4 * Jip J. Dekker <jip.dekker@monash.edu>
5 *
6 * Contributing authors:
7 * Mikael Zayenz Lagerkvist <lagerkvist@gecode.dev>
8 *
9 * Copyright:
10 * Jip J. Dekker, 2026
11 *
12 * This file is part of Gecode, the generic constraint
13 * development environment:
14 * http://www.gecode.org
15 *
16 * Permission is hereby granted, free of charge, to any person obtaining
17 * a copy of this software and associated documentation files (the
18 * "Software"), to deal in the Software without restriction, including
19 * without limitation the rights to use, copy, modify, merge, publish,
20 * distribute, sublicense, and/or sell copies of the Software, and to
21 * permit persons to whom the Software is furnished to do so, subject to
22 * the following conditions:
23 *
24 * The above copyright notice and this permission notice shall be
25 * included in all copies or substantial portions of the Software.
26 *
27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 *
35 */
36
37#if defined(_WIN32)
38#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
39#undef _WIN32_WINNT
40#define _WIN32_WINNT 0x0600
41#endif
42#if !defined(WINVER) || (WINVER < 0x0600)
43#undef WINVER
44#define WINVER 0x0600
45#endif
46#elif !defined(_GNU_SOURCE)
47#define _GNU_SOURCE 1
48#endif
49
50#include <gecode/kernel.hh>
51#include <gecode/flatzinc.hh>
55
56#include <atomic>
57#include <cassert>
58#include <cerrno>
59#include <charconv>
60#include <cctype>
61#include <cstdio>
62#include <cstdlib>
63#include <cstdint>
64#include <exception>
65#include <locale>
66#include <memory>
67#include <limits>
68#include <sstream>
69#include <string>
70#include <vector>
71
72#ifdef _WIN32
73#ifndef NOMINMAX
74#define NOMINMAX 1 // Ensure the words min/max remain available
75#endif
76#include <windows.h>
77#else
78#include <dlfcn.h>
79#endif
80
81#ifdef GECODE_HAS_THREADS
82#include <thread>
83#endif
84
85namespace Gecode {
86namespace FlatZinc {
87
88
89namespace {
90
91#ifdef _WIN32
92std::wstring
93utf8_to_wide(const std::string &s) {
94 if (s.empty()) {
95 return std::wstring();
96 }
97 int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(),
98 static_cast<int>(s.size()), NULL, 0);
99 if (n == 0) {
100 throw Error("Blackbox", "Invalid UTF-8 string in blackbox path or argument");
101 }
102 std::wstring w(static_cast<size_t>(n), L'\0');
103 if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(),
104 static_cast<int>(s.size()), &w[0], n) == 0) {
105 throw Error("Blackbox", "Invalid UTF-8 string in blackbox path or argument");
106 }
107 return w;
108}
109
110std::string
111windows_error(const std::string &prefix, DWORD err) {
112 return prefix + " (Windows error " + std::to_string(err) + ")";
113}
114
115
116bool
117has_dll_suffix(const std::string &name) {
118 if (name.size() < 4) {
119 return false;
120 }
121 const char *suffix = ".dll";
122 for (size_t i = 0; i < 4; i++) {
123 if (std::tolower(static_cast<unsigned char>(name[name.size() - 4 + i])) !=
124 suffix[i]) {
125 return false;
126 }
127 }
128 return true;
129}
130
131std::vector<std::string>
132dll_candidates(const std::string &name) {
133 std::vector<std::string> candidates;
134 candidates.push_back(name);
135 if (!has_dll_suffix(name)) {
136 candidates.push_back(name + ".dll");
137 }
138 const size_t separator = name.find_last_of("\\/");
139 const std::string directory =
140 (separator == std::string::npos) ? std::string() :
141 name.substr(0, separator + 1);
142 const std::string basename =
143 (separator == std::string::npos) ? name : name.substr(separator + 1);
144 if (basename.compare(0, 3, "lib") != 0) {
145 std::string prefixed = directory + "lib" + basename;
146 if (!has_dll_suffix(prefixed)) {
147 prefixed += ".dll";
148 }
149 candidates.push_back(prefixed);
150 }
151 return candidates;
152}
153
154
155void
156close_library(void *library) {
157 if (library != nullptr) {
158 FreeLibrary(static_cast<HMODULE>(library));
159 }
160}
161#else
162void
163close_library(void *library) {
164 if (library != nullptr) {
165 dlclose(library);
166 }
167}
168
169#endif
170
171
172template<class T>
173T
174library_symbol(void *library, const char *name, unsigned int stdcall_bytes) {
175#ifdef _WIN32
176 FARPROC symbol = GetProcAddress(static_cast<HMODULE>(library), name);
177#if defined(_M_IX86) || defined(__i386__)
178 if (symbol == nullptr) {
179 const std::string decorated =
180 std::string("_") + name + "@" + std::to_string(stdcall_bytes);
181 symbol = GetProcAddress(static_cast<HMODULE>(library), decorated.c_str());
182 }
183 if (symbol == nullptr) {
184 const std::string decorated =
185 std::string(name) + "@" + std::to_string(stdcall_bytes);
186 symbol = GetProcAddress(static_cast<HMODULE>(library), decorated.c_str());
187 }
188#else
189 (void)stdcall_bytes;
190#endif
191 return reinterpret_cast<T>(symbol);
192#else
193 (void)stdcall_bytes;
194 T symbol = nullptr;
195 *(void **)(&symbol) = dlsym(library, name);
196 return symbol;
197#endif
198}
199
200void
201check_int(int64_t v, const char *source, size_t i) {
202 if (!Int::Limits::valid(static_cast<long long int>(v))) {
203 throw Error("Blackbox", std::string(source) + " integer " +
204 std::to_string(i) +
205 " is outside Gecode's integer range");
206 }
207}
208
209#ifdef GECODE_HAS_FLOAT_VARS
210void
211check_float(double v, const char *source, size_t i) {
212 static_assert(sizeof(double) == sizeof(std::uint64_t) &&
213 std::numeric_limits<double>::is_iec559,
214 "blackbox floats must use IEEE-754 binary64");
215 std::uint64_t bits = 0;
216 const volatile unsigned char *raw =
217 reinterpret_cast<const volatile unsigned char *>(&v);
218 unsigned char *target = reinterpret_cast<unsigned char *>(&bits);
219 for (size_t j = 0; j < sizeof(bits); j++) {
220 target[j] = raw[j];
221 }
222 if (((bits & UINT64_C(0x7ff0000000000000)) ==
223 UINT64_C(0x7ff0000000000000)) ||
224 (v < Float::Limits::min) || (v > Float::Limits::max)) {
225 throw Error("Blackbox", std::string(source) + " float " +
226 std::to_string(i) +
227 " is not a finite value in Gecode's floating "
228 "point range");
229 }
230}
231
232void
233check_floats(const std::vector<double> &v, const char *source) {
234 for (size_t i = 0; i < v.size(); i++) {
235 check_float(v[i], source, i);
236 }
237}
238#endif
239
240} // namespace
241
242#ifdef GECODE_HAS_THREADS
244public:
245 std::thread::id owner;
246 void *value;
247
248 Instance(const std::thread::id &owner0, void *value0)
249 : owner(owner0), value(value0) {}
250};
251#endif
252
253BlackBoxLibrary::BlackBoxLibrary(const std::string &name,
254 const std::vector<std::string> &args)
255 : library(nullptr), library_fzn_init(nullptr), library_fzn_clone(nullptr),
256 library_fzn_blackbox(nullptr), library_fzn_free(nullptr),
257 root_instance(nullptr)
258{
259 std::string loadError;
260 void *loaded = nullptr;
261#ifdef _WIN32
262 DWORD err = ERROR_FILE_NOT_FOUND;
263 std::string failed_candidate = name;
264 for (const std::string &candidate : dll_candidates(name)) {
265 loaded = LoadLibraryW(utf8_to_wide(candidate).c_str());
266 if (loaded != nullptr) {
267 break;
268 }
269 failed_candidate = candidate;
270 err = GetLastError();
271 }
272 if (loaded == nullptr) {
273 loadError = std::string("unable to locate library `") + name + "' (" +
274 windows_error("LoadLibraryW failed for `" + failed_candidate +
275 "'", err) + ")";
276 }
277#else
278 loaded = dlopen(name.c_str(), RTLD_LAZY);
279 if (!loaded) {
280 loadError = std::string(dlerror());
281 loaded = dlopen((name + ".so").c_str(), RTLD_NOW);
282 }
283 if (!loaded) {
284 loaded = dlopen((std::string("lib") + name + ".so").c_str(), RTLD_NOW);
285 }
286#ifdef __APPLE__
287 if (!loaded) {
288 loaded = dlopen((name + ".dylib").c_str(), RTLD_NOW);
289 }
290 if (!loaded) {
291 loaded = dlopen((std::string("lib") + name + ".dylib").c_str(), RTLD_NOW);
292 }
293#endif
294#endif
295 if (!loaded) {
296 throw Error("Blackbox", "Unable to open dynamic library: " + loadError);
297 }
298
299 bool root_initialized = false;
300 try {
301 // fzn_blackbox is the only required entry point.
302#ifndef _WIN32
303 dlerror();
304#endif
305 library_fzn_blackbox =
306 library_symbol<decltype(library_fzn_blackbox)>(loaded, "fzn_blackbox",
307 36);
308 std::string symError(".");
309 if (library_fzn_blackbox == nullptr) {
310#ifdef _WIN32
311 symError += " (" +
312 windows_error("GetProcAddress failed", GetLastError()) + ")";
313#else
314 const char *error = dlerror();
315 if (error != nullptr) {
316 symError += std::string(": ") + error;
317 }
318#endif
319 throw Error("Blackbox",
320 "Unable to find symbol `fzn_blackbox` in dynamic library" +
321 symError);
322 }
323
325 library_symbol<decltype(library_fzn_init)>(loaded, "fzn_init", 8);
327 library_symbol<decltype(library_fzn_clone)>(loaded, "fzn_clone", 4);
328 library_fzn_free =
329 library_symbol<decltype(library_fzn_free)>(loaded, "fzn_free", 4);
330 if ((library_fzn_init != nullptr) && (library_fzn_clone == nullptr)) {
331 throw Error("Blackbox",
332 "Dynamic library exports `fzn_init` but not `fzn_clone`");
333 }
334 if (library_fzn_init != nullptr) {
335 std::vector<const char *> argv;
336 argv.reserve(args.size());
337 for (const std::string &arg : args) {
338 argv.push_back(arg.c_str());
339 }
340 root_instance = library_fzn_init(argv.data(), argv.size());
341 root_initialized = true;
342 }
343 library = loaded;
344 } catch (...) {
345 if (root_initialized && (library_fzn_free != nullptr)) {
346 try {
347 library_fzn_free(root_instance);
348 } catch (...) {}
349 }
350 close_library(loaded);
351 throw;
352 }
353}
354
356 if ((library_fzn_init != nullptr) && (library_fzn_free != nullptr)) {
357#ifdef GECODE_HAS_THREADS
358 for (Instance *instance : instances) {
359 try {
360 library_fzn_free(instance->value);
361 } catch (...) {}
362 }
363#endif
364 try {
365 library_fzn_free(root_instance);
366 } catch (...) {}
367 }
368#ifdef GECODE_HAS_THREADS
369 for (Instance *instance : instances) {
370 delete instance;
371 }
372#endif
373 close_library(library);
374}
375
376#ifdef GECODE_HAS_THREADS
379 const std::thread::id owner = std::this_thread::get_id();
380 Support::Lock lock(mutex);
381 // Each live thread has exclusive access to its selected instance.
382 for (Instance *instance : instances) {
383 if (instance->owner == owner) {
384 return instance;
385 }
386 }
387 void *value = library_fzn_clone(root_instance);
388 Instance *clone = nullptr;
389 try {
390 clone = new Instance(owner, value);
391 instances.push_back(clone);
392 return clone;
393 } catch (...) {
394 delete clone;
395 if (library_fzn_free != nullptr) {
396 try {
397 library_fzn_free(value);
398 } catch (...) {}
399 }
400 throw;
401 }
402}
403#endif
404
405void
407#ifdef GECODE_HAS_THREADS
408 if (library_fzn_init != nullptr) {
409 Instance *selected = this->instance();
410 library_fzn_blackbox(selected->value,
411 call.int_input.data(), call.int_input.size(),
412 call.float_input.data(), call.float_input.size(),
413 call.int_output.data(), call.int_output.size(),
414 call.float_output.data(), call.float_output.size());
415 } else {
416 library_fzn_blackbox(nullptr,
417 call.int_input.data(), call.int_input.size(),
418 call.float_input.data(), call.float_input.size(),
419 call.int_output.data(), call.int_output.size(),
420 call.float_output.data(), call.float_output.size());
421 }
422#else
423 library_fzn_blackbox(root_instance,
424 call.int_input.data(), call.int_input.size(),
425 call.float_input.data(), call.float_input.size(),
426 call.int_output.data(), call.int_output.size(),
427 call.float_output.data(), call.float_output.size());
428#endif
429 for (size_t i = 0; i < call.int_output.size(); ++i) {
430 check_int(call.int_output[i], "library output", i);
431 }
432#ifdef GECODE_HAS_FLOAT_VARS
433 check_floats(call.float_output, "library output");
434#endif
435}
436
437
438BlackBoxExec::BlackBoxExec(const std::string &program0,
439 const std::vector<std::string> &args0)
440 : program(program0), args(args0) {}
441
443 Support::Lock lock(mutex);
445 delete s;
446 }
447 sessions.clear();
448}
449
450
452 Support::Lock lock(mutex);
454 if (s->owned_by_current_thread()) {
455 return *s;
456 }
457 }
458 std::unique_ptr<BlackBoxProcessSession> s(
460 BlackBoxProcessSession *r = s.get();
461 sessions.push_back(r);
462 s.release();
463 return *r;
464}
465
466std::string
468 // Construct program input: comma-separated integers, a semicolon, then
469 // comma-separated floats, terminated by a newline (e.g. "5,-7;2.5,1.125\n").
470 std::ostringstream out;
471 out.imbue(std::locale::classic());
472 out.precision(std::numeric_limits<double>::max_digits10);
473 for (size_t i = 0; i < call.int_input.size(); ++i) {
474 if (i != 0) {
475 out << ",";
476 }
477 out << call.int_input[i];
478 }
479 out << ";";
480 for (size_t i = 0; i < call.float_input.size(); ++i) {
481 if (i != 0) {
482 out << ",";
483 }
484 out << call.float_input[i];
485 }
486 out << "\n";
487 return out.str();
488}
489
490void
491decode_blackbox_response(const std::string& response, BlackBoxCall& call) {
492 if (response.find('\0') != std::string::npos) {
493 throw Error("BlackBoxExec",
494 "Blackbox process response contains NUL data.");
495 }
496 // Parse the response in a single left-to-right pass: comma-separated
497 // integers, a semicolon, then comma-separated floats (e.g. "5,-7;2.5,1.125\n").
498 const char *p = response.c_str();
499 auto skip_ws = [](const char *&q) {
500 while (*q == ' ' || *q == '\t' || *q == '\r') {
501 ++q;
502 }
503 };
504 auto skip_final_ws = [](const char *&q) {
505 while (*q == ' ' || *q == '\t' || *q == '\r' || *q == '\n') {
506 ++q;
507 }
508 };
509 auto value_end = [](const char *q) {
510 while (*q != ',' && *q != ';' && *q != '\n' && *q != '\0') {
511 ++q;
512 }
513 return q;
514 };
515 auto check_integer_tail = [](const char *q, const char *end) {
516 while (q != end) {
517 if (*q != ' ' && *q != '\t' && *q != '\r') {
518 return false;
519 }
520 ++q;
521 }
522 return true;
523 };
524 auto check_number_tail = [](std::istringstream &in) {
525 char c;
526 while (in.get(c)) {
527 if (c != ' ' && c != '\t' && c != '\r') {
528 return false;
529 }
530 }
531 return true;
532 };
533 for (size_t i = 0; i < call.int_output.size(); ++i) {
534 skip_ws(p);
535 const char *end = value_end(p);
536 const char *integer = p;
537 if (*integer == '+') {
538 ++integer;
539 }
540 int64_t value;
541 const std::from_chars_result parsed =
542 std::from_chars(integer, end, value);
543 if ((parsed.ptr == integer) || (parsed.ec != std::errc()) ||
544 !check_integer_tail(parsed.ptr, end)) {
545 throw Error("BlackBoxExec", "Failed to read output integer " +
546 std::to_string(i) +
547 " from blackbox process output, " +
548 std::to_string(call.int_output.size()) +
549 " integer values were expected.");
550 }
551 check_int(value, "blackbox process output", i);
552 call.int_output[i] = value;
553 p = end;
554 skip_ws(p);
555 if (i + 1 < call.int_output.size()) {
556 if (*p != ',') {
557 throw Error("BlackBoxExec",
558 "Blackbox process response is missing an integer output "
559 "separator.");
560 }
561 ++p;
562 }
563 }
564 skip_ws(p);
565 if (*p != ';') {
566 throw Error("BlackBoxExec",
567 "Blackbox process response is missing the `;' separator between "
568 "the integer and floating point outputs.");
569 }
570 ++p;
571 for (size_t i = 0; i < call.float_output.size(); ++i) {
572 skip_ws(p);
573 const char *end = value_end(p);
574 std::istringstream in(std::string(p, end));
575 in.imbue(std::locale::classic());
576 double v;
577 if (!(in >> v) || !check_number_tail(in)) {
578 throw Error("BlackBoxExec", "Failed to read output float " +
579 std::to_string(i) +
580 " from blackbox process output, " +
581 std::to_string(call.float_output.size()) +
582 " floating point values were expected.");
583 }
584#ifdef GECODE_HAS_FLOAT_VARS
585 check_float(v, "blackbox process output", i);
586#endif
587 call.float_output[i] = v;
588 p = end;
589 skip_ws(p);
590 if (i + 1 < call.float_output.size()) {
591 if (*p != ',') {
592 throw Error("BlackBoxExec",
593 "Blackbox process response is missing a floating point "
594 "output separator.");
595 }
596 ++p;
597 }
598 }
599 skip_final_ws(p);
600 if (*p != '\0') {
601 throw Error("BlackBoxExec",
602 "Blackbox process response contains trailing data.");
603 }
604}
605
606void
608 const std::string response =
610 decode_blackbox_response(response, call);
611}
612
613} // namespace FlatZinc
614} // namespace Gecode
615
616// STATISTICS: flatzinc-other
BlackBoxProcessSession & session(void)
std::vector< std::string > args
BlackBoxExec(const std::string &program, const std::vector< std::string > &args)
std::vector< BlackBoxProcessSession * > sessions
void run(BlackBoxCall &call) override
Instance(const std::thread::id &owner0, void *value0)
void *GECODE_BLACKBOX_CALL * library_fzn_clone(void *)
void *GECODE_BLACKBOX_CALL * library_fzn_init(const char **, size_t)
BlackBoxLibrary(const std::string &name, const std::vector< std::string > &args)
void run(BlackBoxCall &call) override
std::vector< Instance * > instances
Platform process session used by the executable blackbox backend.
virtual std::string exchange(const std::string &request)=0
Exception class for FlatZinc errors
Definition flatzinc.hh:727
A lock as a scoped frontend for a mutex.
Definition thread.hpp:114
std::string encode_blackbox_request(const BlackBoxCall &call)
Encode one request for the executable backend's line protocol.
BlackBoxProcessSession * create_blackbox_process(const std::string &, const std::vector< std::string > &)
Create the process implementation selected for the target platform.
void decode_blackbox_response(const std::string &response, BlackBoxCall &call)
Decode and validate one response from the executable backend.
const FloatNum max
Largest allowed float value.
Definition float.hh:844
const FloatNum min
Smallest allowed float value.
Definition float.hh:846
bool valid(int n)
Return whether n is in range.
Definition limits.hpp:37
Gecode toplevel namespace
Inputs and pre-sized output buffers for one backend call.
const std::vector< int64_t > & int_input
std::vector< int64_t > & int_output
const std::vector< double > & float_input
std::vector< double > & float_output