diff --git a/src/autoschedulers/adams2019/ASLog.h b/src/autoschedulers/adams2019/ASLog.h index 9ba9844ce342..956845880569 100644 --- a/src/autoschedulers/adams2019/ASLog.h +++ b/src/autoschedulers/adams2019/ASLog.h @@ -5,6 +5,7 @@ // libHalide, so (despite the namespace) we are better off not // including Halide.h, lest we reference something we won't have available +#include #include #include #include @@ -28,6 +29,12 @@ class aslog { return *this; } + std::ostream &get_ostream() { + // It is an error to call this for an aslog() instance that cannot log. + assert(logging); + return std::cerr; + } + static int aslog_level(); }; diff --git a/src/autoschedulers/adams2019/AutoSchedule.cpp b/src/autoschedulers/adams2019/AutoSchedule.cpp index 5386e061bf00..56f8ed3dbda5 100644 --- a/src/autoschedulers/adams2019/AutoSchedule.cpp +++ b/src/autoschedulers/adams2019/AutoSchedule.cpp @@ -117,42 +117,45 @@ struct ProgressBar { if (!draw_progress_bar) { return; } + auto &os = aslog(ProgressBarLogLevel).get_ostream(); counter++; const int bits = 11; if (counter & ((1 << bits) - 1)) { return; } const int pos = (int)(progress * 78); - aslog(0) << "["; + os << "["; for (int j = 0; j < 78; j++) { if (j < pos) { - aslog(0) << "."; + os << "."; } else if (j - 1 < pos) { - aslog(0) << "/-\\|"[(counter >> bits) % 4]; + os << "/-\\|"[(counter >> bits) % 4]; } else { - aslog(0) << " "; + os << " "; } } - aslog(0) << "]"; + os << "]"; for (int j = 0; j < 80; j++) { - aslog(0) << "\b"; + os << "\b"; } } void clear() { if (counter) { + auto &os = aslog(ProgressBarLogLevel).get_ostream(); for (int j = 0; j < 80; j++) { - aslog(0) << " "; + os << " "; } for (int j = 0; j < 80; j++) { - aslog(0) << "\b"; + os << "\b"; } } } private: uint32_t counter = 0; - const bool draw_progress_bar = isatty(2); + static constexpr int ProgressBarLogLevel = 1; + const bool draw_progress_bar = isatty(2) && aslog::aslog_level() >= ProgressBarLogLevel; }; // Get the HL_RANDOM_DROPOUT environment variable. Purpose of this is described above. @@ -290,10 +293,6 @@ IntrusivePtr optimal_schedule_pass(FunctionDAG &dag, std::function &&)> enqueue_new_children = [&](IntrusivePtr &&s) { - // aslog(0) << "\n** Generated child: "; - // s->dump(); - // s->calculate_cost(dag, params, nullptr, true); - // Each child should have one more decision made than its parent state. internal_assert(s->num_decisions_made == s->parent->num_decisions_made + 1); @@ -338,7 +337,7 @@ IntrusivePtr optimal_schedule_pass(FunctionDAG &dag, } if ((int)pending.size() > beam_size * 10000) { - aslog(0) << "Warning: Huge number of states generated (" << pending.size() << ").\n"; + aslog(1) << "*** Warning: Huge number of states generated (" << pending.size() << ").\n"; } expanded = 0; @@ -436,25 +435,26 @@ IntrusivePtr optimal_schedule_pass(FunctionDAG &dag, // The user has set HL_CYOS, and wants to navigate the // search space manually. Discard everything in the queue // except for the user-chosen option. - aslog(0) << "\n--------------------\n"; - aslog(0) << "Select a schedule:\n"; + std::cout << "\n--------------------\n"; + std::cout << "Select a schedule:\n"; for (int choice_label = (int)q.size() - 1; choice_label >= 0; choice_label--) { auto state = q[choice_label]; - aslog(0) << "\n[" << choice_label << "]:\n"; - state->dump(); - state->calculate_cost(dag, params, cost_model, cache->options, memory_limit, true); + std::cout << "\n[" << choice_label << "]:\n"; + state->dump(std::cout); + constexpr int verbosity_level = 0; // always + state->calculate_cost(dag, params, cost_model, cache->options, memory_limit, verbosity_level); } cost_model->evaluate_costs(); // Select next partial schedule to expand. int selection = -1; while (selection < 0 || selection >= (int)q.size()) { - aslog(0) << "\nEnter selection: "; + std::cout << "\nEnter selection: "; std::cin >> selection; } auto selected = q[selection]; - selected->dump(); + selected->dump(std::cout); q.clear(); q.emplace(std::move(selected)); } @@ -508,11 +508,16 @@ IntrusivePtr optimal_schedule(FunctionDAG &dag, tick.clear(); - if (aslog::aslog_level() == 0) { - aslog(0) << "Pass " << i << " of " << num_passes << ", cost: " << pass->cost << ", time (ms): " << milli << "\n"; - } else { - aslog(0) << "Pass " << i << " result: "; - pass->dump(); + switch (aslog::aslog_level()) { + case 0: + // Silence + break; + case 1: + aslog(1) << "Pass " << i << " of " << num_passes << ", cost: " << pass->cost << ", time (ms): " << milli << "\n"; + break; + default: + aslog(2) << "Pass " << i << " result: "; + pass->dump(aslog(2).get_ostream()); } if (i == 0 || pass->cost < best->cost) { @@ -522,11 +527,11 @@ IntrusivePtr optimal_schedule(FunctionDAG &dag, } } - aslog(0) << "Best cost: " << best->cost << "\n"; + aslog(1) << "Best cost: " << best->cost << "\n"; if (options.cache_blocks) { - aslog(0) << "Cache (block) hits: " << cache.cache_hits << "\n"; - aslog(0) << "Cache (block) misses: " << cache.cache_misses << "\n"; + aslog(1) << "Cache (block) hits: " << cache.cache_hits << "\n"; + aslog(1) << "Cache (block) misses: " << cache.cache_misses << "\n"; } return best; @@ -540,7 +545,7 @@ void generate_schedule(const std::vector &outputs, const Target &target, const MachineParams ¶ms, AutoSchedulerResults *auto_scheduler_results) { - aslog(0) << "generate_schedule for target=" << target.to_string() << "\n"; + aslog(1) << "generate_schedule for target=" << target.to_string() << "\n"; // Start a timer HALIDE_TIC; @@ -554,7 +559,7 @@ void generate_schedule(const std::vector &outputs, if (!seed_str.empty()) { seed = atoi(seed_str.c_str()); } - aslog(1) << "Dropout seed = " << seed << "\n"; + aslog(2) << "Dropout seed = " << seed << "\n"; std::mt19937 rng((uint32_t)seed); // Get the beam size @@ -576,8 +581,8 @@ void generate_schedule(const std::vector &outputs, // Analyse the Halide algorithm and construct our abstract representation of it FunctionDAG dag(outputs, params, target); - if (aslog::aslog_level() > 0) { - dag.dump(); + if (aslog::aslog_level() >= 2) { + dag.dump(aslog(2).get_ostream()); } // Construct a cost model to use to evaluate states. Currently we @@ -602,14 +607,14 @@ void generate_schedule(const std::vector &outputs, aslog(1) << "** Optimal schedule:\n"; // Just to get the debugging prints to fire - optimal->calculate_cost(dag, params, cost_model.get(), cache_options, memory_limit, aslog::aslog_level() > 0); + optimal->calculate_cost(dag, params, cost_model.get(), cache_options, memory_limit, /*verbosity_level*/ 1); // Apply the schedules to the pipeline optimal->apply_schedule(dag, params); // Print out the schedule - if (aslog::aslog_level() > 0) { - optimal->dump(); + if (aslog::aslog_level() >= 2) { + optimal->dump(aslog(2).get_ostream()); } string schedule_file = get_env_variable("HL_SCHEDULE_FILE"); diff --git a/src/autoschedulers/adams2019/DefaultCostModel.cpp b/src/autoschedulers/adams2019/DefaultCostModel.cpp index 27416a272820..630628c4354e 100644 --- a/src/autoschedulers/adams2019/DefaultCostModel.cpp +++ b/src/autoschedulers/adams2019/DefaultCostModel.cpp @@ -232,18 +232,18 @@ float DefaultCostModel::backprop(const Runtime::Buffer &true_runtim *(cost_ptrs(i)) = dst(i); if (std::isnan(dst(i))) { any_nans = true; - aslog(0) << "Prediction " << i << " is NaN. True runtime is " << true_runtimes(i) << "\n"; - aslog(0) << "Checking pipeline features for NaNs...\n"; + aslog(1) << "Prediction " << i << " is NaN. True runtime is " << true_runtimes(i) << "\n"; + aslog(1) << "Checking pipeline features for NaNs...\n"; pipeline_feat_queue.for_each_value([&](float f) { if (std::isnan(f)) abort(); }); - aslog(0) << "None found\n"; - aslog(0) << "Checking schedule features for NaNs...\n"; + aslog(1) << "None found\n"; + aslog(1) << "Checking schedule features for NaNs...\n"; schedule_feat_queue.for_each_value([&](float f) { if (std::isnan(f)) abort(); }); - aslog(0) << "None found\n"; - aslog(0) << "Checking network weights for NaNs...\n"; + aslog(1) << "None found\n"; + aslog(1) << "Checking network weights for NaNs...\n"; weights.for_each_buffer([&](const Runtime::Buffer &buf) { buf.for_each_value([&](float f) { if (std::isnan(f)) abort(); }); }); - aslog(0) << "None found\n"; + aslog(1) << "None found\n"; } internal_assert(true_runtimes(i) > 0); } diff --git a/src/autoschedulers/adams2019/Featurization.h b/src/autoschedulers/adams2019/Featurization.h index 0e050bcc55a4..5f065b31feb6 100644 --- a/src/autoschedulers/adams2019/Featurization.h +++ b/src/autoschedulers/adams2019/Featurization.h @@ -99,8 +99,7 @@ struct PipelineFeatures { // Each row sums to 1 or 0. Each column sums to 1. f(z, y, x, 4) int slice_accesses[(int)AccessType::NumAccessTypes][(int)ScalarType::NumScalarTypes] = {}; - template - void dump(OS &os) const { + void dump(std::ostream &os) const { for (int i = 0; i < (int)ScalarType::NumScalarTypes; i++) { const char *type_names[] = {"Bool", "UInt8", "UInt16", "UInt32", "UInt64", "Float", "Double"}; // Skip printing for types not used @@ -157,10 +156,6 @@ struct PipelineFeatures { << slice_accesses[3][i] << "\n"; } } - void dump() const { - auto os = aslog(0); - dump(os); - } }; // The schedule-dependent portion of the featurization of a stage @@ -314,8 +309,7 @@ struct ScheduleFeatures { double working_set_at_realization = 0; double working_set_at_root = 0; - template - void dump(OS &os) const { + void dump(std::ostream &os) const { os << " num_realizations: " << num_realizations << "\n" << " num_productions: " << num_productions << "\n" << " points_computed_per_realization: " << points_computed_per_realization << "\n" @@ -356,10 +350,6 @@ struct ScheduleFeatures { << " working_set_at_realization: " << working_set_at_realization << "\n" << " working_set_at_root: " << working_set_at_root << "\n"; } - void dump() const { - auto os = aslog(0); - dump(os); - } bool equal(const ScheduleFeatures &other) const { const size_t n_features = ScheduleFeatures::num_features(); diff --git a/src/autoschedulers/adams2019/FunctionDAG.cpp b/src/autoschedulers/adams2019/FunctionDAG.cpp index 7f4c41045804..72bc9dc7e0e1 100644 --- a/src/autoschedulers/adams2019/FunctionDAG.cpp +++ b/src/autoschedulers/adams2019/FunctionDAG.cpp @@ -307,42 +307,44 @@ class Featurizer : public IRVisitor { } // namespace -void LoadJacobian::dump(const char *prefix) const { +void LoadJacobian::dump(std::ostream &os, const char *prefix) const { if (count() > 1) { - aslog(0) << prefix << count() << " x\n"; + os << prefix << count() << " x\n"; } for (size_t i = 0; i < producer_storage_dims(); i++) { - aslog(0) << prefix << " ["; + os << prefix << " ["; for (size_t j = 0; j < consumer_loop_dims(); j++) { const auto &c = (*this)(i, j); if (!c.exists) { - aslog(0) << " _ "; + os << " _ "; } else if (c.denominator == 1) { - aslog(0) << " " << c.numerator << " "; + os << " " << c.numerator << " "; } else { - aslog(0) << c.numerator << "/" << c.denominator << " "; + os << c.numerator << "/" << c.denominator << " "; } } - aslog(0) << "]\n"; + os << "]\n"; } - aslog(0) << "\n"; + os << "\n"; } void BoundContents::validate() const { for (int i = 0; i < layout->total_size; i++) { auto p = data()[i]; if (p.max() < p.min()) { - aslog(0) << "Bad bounds object:\n"; + std::ostringstream err; + err << "Bad bounds object:\n"; for (int j = 0; j < layout->total_size; j++) { if (i == j) { - aslog(0) << "=> "; + err << "=> "; } else { - aslog(0) << " "; + err << " "; } - aslog(0) << j << ": " << data()[j].min() << ", " << data()[j].max() << "\n"; + err << j << ": " << data()[j].min() << ", " << data()[j].max() << "\n"; } - internal_error << "Aborting"; + err << "Aborting"; + internal_error << err.str(); } } } @@ -1039,8 +1041,7 @@ void FunctionDAG::featurize() { } } -template -void FunctionDAG::dump_internal(OS &os) const { +void FunctionDAG::dump(std::ostream &os) const { for (const Node &n : nodes) { os << "Node: " << n.func.name() << "\n" << " Symbolic region required: \n"; @@ -1076,21 +1077,11 @@ void FunctionDAG::dump_internal(OS &os) const { os << " Load Jacobians:\n"; for (const auto &jac : e.load_jacobians) { - jac.dump(" "); + jac.dump(os, " "); } } } -void FunctionDAG::dump() const { - auto os = aslog(0); - dump_internal(os); -} - -std::ostream &FunctionDAG::dump(std::ostream &os) const { - dump_internal(os); - return os; -} - } // namespace Autoscheduler } // namespace Internal } // namespace Halide diff --git a/src/autoschedulers/adams2019/FunctionDAG.h b/src/autoschedulers/adams2019/FunctionDAG.h index a2bebb8d095e..44f0cf315db8 100644 --- a/src/autoschedulers/adams2019/FunctionDAG.h +++ b/src/autoschedulers/adams2019/FunctionDAG.h @@ -205,7 +205,7 @@ class LoadJacobian { return result; } - void dump(const char *prefix) const; + void dump(std::ostream &os, const char *prefix) const; }; // Classes to represent a concrete set of bounds for a Func. A Span is @@ -565,16 +565,12 @@ struct FunctionDAG { // analysis. This is done once up-front before the tree search. FunctionDAG(const vector &outputs, const MachineParams ¶ms, const Target &target); - void dump() const; - std::ostream &dump(std::ostream &os) const; + void dump(std::ostream &os) const; private: // Compute the featurization for the entire DAG void featurize(); - template - void dump_internal(OS &os) const; - public: // This class uses a lot of internal pointers, so we'll make it uncopyable/unmovable. FunctionDAG(const FunctionDAG &other) = delete; diff --git a/src/autoschedulers/adams2019/LoopNest.cpp b/src/autoschedulers/adams2019/LoopNest.cpp index 718624326d44..a5cf19a61274 100644 --- a/src/autoschedulers/adams2019/LoopNest.cpp +++ b/src/autoschedulers/adams2019/LoopNest.cpp @@ -1091,24 +1091,24 @@ const Bound &LoopNest::get_bounds(const FunctionDAG::Node *f) const { } // Recursively print a loop nest representation to stderr -void LoopNest::dump(string prefix, const LoopNest *parent) const { +void LoopNest::dump(std::ostream &os, string prefix, const LoopNest *parent) const { if (!is_root()) { // Non-root nodes always have parents. internal_assert(parent != nullptr); - aslog(0) << prefix << node->func.name(); + os << prefix << node->func.name(); prefix += " "; for (size_t i = 0; i < size.size(); i++) { - aslog(0) << " " << size[i]; + os << " " << size[i]; // The vectorized loop gets a 'v' suffix if (innermost && i == (size_t)vectorized_loop_index) { - aslog(0) << "v"; + os << "v"; } // Loops that have a known constant size get a // 'c'. Useful for knowing what we can unroll. if (parent->get_bounds(node)->loops(stage->index, i).constant_extent()) { - aslog(0) << "c"; + os << "c"; } } @@ -1117,31 +1117,31 @@ void LoopNest::dump(string prefix, const LoopNest *parent) const { const auto &bounds = get_bounds(node); for (size_t i = 0; i < size.size(); i++) { const auto &p = bounds->loops(stage->index, i); - aslog(0) << " [" << p.first << ", " << p.second << "]"; + os << " [" << p.first << ", " << p.second << "]"; } */ - aslog(0) << " (" << vectorized_loop_index << ", " << vector_dim << ")"; + os << " (" << vectorized_loop_index << ", " << vector_dim << ")"; } if (tileable) { - aslog(0) << " t"; + os << " t"; } if (innermost) { - aslog(0) << " *\n"; + os << " *\n"; } else if (parallel) { - aslog(0) << " p\n"; + os << " p\n"; } else { - aslog(0) << "\n"; + os << "\n"; } for (const auto *p : store_at) { - aslog(0) << prefix << "realize: " << p->func.name() << "\n"; + os << prefix << "realize: " << p->func.name() << "\n"; } for (size_t i = children.size(); i > 0; i--) { - children[i - 1]->dump(prefix, this); + children[i - 1]->dump(os, prefix, this); } for (auto it = inlined.begin(); it != inlined.end(); it++) { - aslog(0) << prefix << "inlined: " << it.key()->func.name() << " " << it.value() << "\n"; + os << prefix << "inlined: " << it.key()->func.name() << " " << it.value() << "\n"; } } @@ -1504,7 +1504,7 @@ vector> LoopNest::compute_in_tiles(const FunctionDA auto tilings = generate_tilings(size, (int)(size.size() - 1), 2, !in_realization); if (tilings.size() > 10000) { - aslog(0) << "Warning: lots of tilings: " << tilings.size() << "\n"; + aslog(1) << "Warning: lots of tilings: " << tilings.size() << "\n"; } for (auto t : tilings) { diff --git a/src/autoschedulers/adams2019/LoopNest.h b/src/autoschedulers/adams2019/LoopNest.h index 20220a3ed4a5..b937d1133da7 100644 --- a/src/autoschedulers/adams2019/LoopNest.h +++ b/src/autoschedulers/adams2019/LoopNest.h @@ -157,7 +157,7 @@ struct LoopNest { const Bound &get_bounds(const FunctionDAG::Node *f) const; // Recursively print a loop nest representation to stderr - void dump(string prefix, const LoopNest *parent) const; + void dump(std::ostream &os, string prefix, const LoopNest *parent) const; // Does this loop nest access the given Func bool calls(const FunctionDAG::Node *f) const; diff --git a/src/autoschedulers/adams2019/State.cpp b/src/autoschedulers/adams2019/State.cpp index 74aa35d0c46d..d85bf91ce6f6 100644 --- a/src/autoschedulers/adams2019/State.cpp +++ b/src/autoschedulers/adams2019/State.cpp @@ -54,10 +54,10 @@ void State::compute_featurization(const FunctionDAG &dag, const MachineParams &p l = consumer_site.compute; } if (!l) { - if (aslog::aslog_level() > 0) { - dump(); - } - internal_error << e->producer->func.name() << " -> " << e->consumer->name << "\n"; + std::ostringstream err; + dump(err); + err << e->producer->func.name() << " -> " << e->consumer->name << "\n"; + internal_error << err.str(); } if (loop) { loop = deepest_common_ancestor(parent, l, loop); @@ -125,18 +125,18 @@ void State::save_featurization(const FunctionDAG &dag, const MachineParams ¶ bool State::calculate_cost(const FunctionDAG &dag, const MachineParams ¶ms, CostModel *cost_model, const CachingOptions &cache_options, - int64_t memory_limit, bool verbose) { + int64_t memory_limit, int verbosity) { StageMap features; compute_featurization(dag, params, &features, cache_options); cost = 0.0f; - if (verbose) { + if (verbosity <= aslog::aslog_level()) { for (auto it = features.begin(); it != features.end(); it++) { const auto &stage = *(it.key()); const auto &feat = it.value(); - aslog(0) << "Schedule features for " << stage.stage.name() << "\n"; - feat.dump(); + aslog(verbosity) << "Schedule features for " << stage.stage.name() << "\n"; + feat.dump(aslog(verbosity).get_ostream()); } } @@ -235,7 +235,7 @@ void State::generate_children(const FunctionDAG &dag, // We don't need to schedule nodes that represent inputs, // and there are no other decisions to be made about them // at this time. - // aslog(0) << "Skipping over scheduling input node: " << node->func.name() << "\n"; + // aslog(1) << "Skipping over scheduling input node: " << node->func.name() << "\n"; auto child = make_child(); child->num_decisions_made++; accept_child(std::move(child)); @@ -243,17 +243,19 @@ void State::generate_children(const FunctionDAG &dag, } if (!node->outgoing_edges.empty() && !root->calls(node)) { - aslog(0) << "In state:\n"; - dump(); - aslog(0) << node->func.name() << " is consumed by:\n"; + std::ostringstream err; + err << "In state:\n"; + dump(err); + err << node->func.name() << " is consumed by:\n"; for (const auto *e : node->outgoing_edges) { - aslog(0) << e->consumer->name << "\n"; - aslog(0) << "Which in turn consumes:\n"; + err << e->consumer->name << "\n"; + err << "Which in turn consumes:\n"; for (const auto *e2 : e->consumer->incoming_edges) { - aslog(0) << " " << e2->producer->func.name() << "\n"; + err << " " << e2->producer->func.name() << "\n"; } } - internal_error << "Pipeline so far doesn't use next Func: " << node->func.name() << "\n"; + err << "Pipeline so far doesn't use next Func: " << node->func.name() << "\n"; + internal_error << err.str(); } int num_children = 0; @@ -520,18 +522,18 @@ void State::generate_children(const FunctionDAG &dag, } if (num_children == 0) { - aslog(0) << "Warning: Found no legal way to schedule " + aslog(1) << "Warning: Found no legal way to schedule " << node->func.name() << " in the following State:\n"; - dump(); + dump(aslog(1).get_ostream()); // All our children died. Maybe other states have had // children. Carry on. } } -void State::dump() const { - aslog(0) << "State with cost " << cost << ":\n"; - root->dump("", nullptr); - aslog(0) << schedule_source; +void State::dump(std::ostream &os) const { + os << "State with cost " << cost << ":\n"; + root->dump(os, "", nullptr); + os << schedule_source; } // Apply the schedule represented by this state to a Halide diff --git a/src/autoschedulers/adams2019/State.h b/src/autoschedulers/adams2019/State.h index e37628f38f2d..592b6db8930e 100644 --- a/src/autoschedulers/adams2019/State.h +++ b/src/autoschedulers/adams2019/State.h @@ -67,7 +67,7 @@ struct State { // otherwise sets `cost` equal to a large value and returns false. bool calculate_cost(const FunctionDAG &dag, const MachineParams ¶ms, CostModel *cost_model, const CachingOptions &cache_options, - int64_t memory_limit, bool verbose = false); + int64_t memory_limit, int verbosity = 99); // Make a child copy of this state. The loop nest is const (we // make mutated copies of it, rather than mutating it), so we can @@ -85,8 +85,8 @@ struct State { std::function &&)> &accept_child, Cache *cache) const; - // Dumps cost, the `root` LoopNest, and then `schedule_source` to `aslog(0)`. - void dump() const; + // Dumps cost, the `root` LoopNest, and then `schedule_source` to `os`. + void dump(std::ostream &os) const; // Apply the schedule represented by this state to a Halide // Pipeline. Also generate source code for the schedule for the diff --git a/src/autoschedulers/adams2019/autotune_loop.sh b/src/autoschedulers/adams2019/autotune_loop.sh index b11aaa1d24ab..f4fd01afa967 100755 --- a/src/autoschedulers/adams2019/autotune_loop.sh +++ b/src/autoschedulers/adams2019/autotune_loop.sh @@ -79,6 +79,12 @@ if [ $(uname -s) = "Darwin" ] && ! which $TIMEOUT_CMD 2>&1 >/dev/null; then fi fi +if [ $(uname -s) = "Darwin" ]; then + SHARED_EXT=dylib +else + SHARED_EXT=so +fi + # Build a single featurization of the pipeline with a random schedule make_featurization() { D=${1} @@ -111,7 +117,7 @@ make_featurization() { target=${HL_TARGET} \ auto_schedule=true \ ${EXTRA_GENERATOR_ARGS} \ - -p ${AUTOSCHED_BIN}/libautoschedule_adams2019.so \ + -p ${AUTOSCHED_BIN}/libautoschedule_adams2019.${SHARED_EXT} \ -s Adams2019 \ 2> ${D}/compile_log.txt || echo "Compilation failed or timed out for ${D}"