[Scheduling] Add an algorithm-independent problem test. (#1413)

This allows testing the `check()`/`verify()` facilities in the Problem class by reading a given schedule from the test case. Tests like this are useful because they let us test new problem models in case a suitable algorithm is still being implemented, or can only live out-of-tree.

I also separated normal and error tests, as per convention.
This commit is contained in:
Julian Oppermann 2021-07-15 09:49:43 +02:00 committed by GitHub
parent dacf8aa2a4
commit 7d89311c07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 194 additions and 91 deletions

View File

@ -1,4 +1,4 @@
//===- TestPasses.cpp - Test passes for scheduling algorithms -===============//
//===- TestPasses.cpp - Test passes for the scheduling infrastructure -----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
//
// This file implements test passes for scheduling algorithms.
// This file implements test passes for scheduling problems and algorithms.
//
//===----------------------------------------------------------------------===//
@ -20,22 +20,10 @@ using namespace circt;
using namespace circt::scheduling;
//===----------------------------------------------------------------------===//
// ASAPScheduler
// Construction helper methods
//===----------------------------------------------------------------------===//
namespace {
struct TestASAPSchedulerPass
: public PassWrapper<TestASAPSchedulerPass, FunctionPass> {
void runOnFunction() override;
};
} // anonymous namespace
void TestASAPSchedulerPass::runOnFunction() {
auto func = getFunction();
OpBuilder builder(func.getContext());
Problem prob(func);
static LogicalResult constructProblem(Problem &prob, FuncOp func) {
// set up catch-all operator type with unit latency
auto unitOpr = prob.getOrInsertOperatorType("unit");
prob.setLatency(unitOpr, 1);
@ -84,12 +72,69 @@ void TestASAPSchedulerPass::runOnFunction() {
// finally, we have two integer indices in range of the operations list
if (failed(prob.insertDependence(
std::make_pair(ops[fromIdx], ops[toIdx])))) {
func->emitError("inserting aux dependence failed");
return signalPassFailure();
return func->emitError("inserting aux dependence failed");
}
}
}
return success();
}
//===----------------------------------------------------------------------===//
// (Basic) Problem
//===----------------------------------------------------------------------===//
namespace {
struct TestProblemPass : public PassWrapper<TestProblemPass, FunctionPass> {
void runOnFunction() override;
};
} // namespace
void TestProblemPass::runOnFunction() {
auto func = getFunction();
Problem prob(func);
if (failed(constructProblem(prob, func))) {
func->emitError("problem construction failed");
return signalPassFailure();
}
if (failed(prob.check())) {
func->emitError("problem check failed");
return signalPassFailure();
}
// get schedule from the test case
for (auto *op : prob.getOperations())
if (auto startTimeAttr = op->getAttrOfType<IntegerAttr>("problemStartTime"))
prob.setStartTime(op, startTimeAttr.getInt());
if (failed(prob.verify())) {
func->emitError("problem verification failed");
return signalPassFailure();
}
}
//===----------------------------------------------------------------------===//
// ASAPScheduler
//===----------------------------------------------------------------------===//
namespace {
struct TestASAPSchedulerPass
: public PassWrapper<TestASAPSchedulerPass, FunctionPass> {
void runOnFunction() override;
};
} // anonymous namespace
void TestASAPSchedulerPass::runOnFunction() {
auto func = getFunction();
Problem prob(func);
if (failed(constructProblem(prob, func))) {
func->emitError("problem construction failed");
return signalPassFailure();
}
if (failed(prob.check())) {
func->emitError("problem check failed");
return signalPassFailure();
@ -105,9 +150,10 @@ void TestASAPSchedulerPass::runOnFunction() {
return signalPassFailure();
}
OpBuilder builder(func.getContext());
for (auto *op : prob.getOperations()) {
unsigned startTime = *prob.getStartTime(op);
op->emitRemark("start time = " + std::to_string(startTime));
op->setAttr("asapStartTime", builder.getI32IntegerAttr(startTime));
}
}
@ -118,8 +164,10 @@ void TestASAPSchedulerPass::runOnFunction() {
namespace circt {
namespace test {
void registerSchedulingTestPasses() {
PassRegistration<TestProblemPass> problemTester(
"test-scheduling-problem", "Import a schedule encoded as attributes");
PassRegistration<TestASAPSchedulerPass> asapTester(
"test-asap-scheduler", "Emit ASAP scheduler's solution as remarks");
"test-asap-scheduler", "Emit ASAP scheduler's solution as attributes");
}
} // namespace test
} // namespace circt

View File

@ -0,0 +1,13 @@
// RUN: circt-opt %s -test-asap-scheduler -verify-diagnostics -split-input-file
// expected-error@+2 {{dependence cycle detected}}
// expected-error@+1 {{scheduling failed}}
func @cyclic_graph() attributes {
auxdeps = [ [0,1], [1,2], [2,3], [3,1] ]
} {
%0 = constant 0 : i32
%1 = constant 1 : i32
%2 = constant 2 : i32
%3 = constant 3 : i32
return
}

View File

@ -0,0 +1,47 @@
// RUN: circt-opt %s -test-scheduling-problem -verify-diagnostics -split-input-file
// expected-error@+2 {{Operator type 'foo' has no latency}}
// expected-error@+1 {{problem check failed}}
func @no_latency() {
%0 = constant 0 : i32
%1 = constant { problemStartTime = 0, opr = "foo" } 1 : i32
return
}
// -----
// expected-error@+1 {{problem verification failed}}
func @no_starttime() {
%0 = constant { problemStartTime = 0 } 0 : i32
%1 = constant 1 : i32 // expected-error {{Operation has no start time}}
return { problemStartTime = 0 }
}
// -----
// expected-error@+2 {{Precedence violated for dependence}}
// expected-error@+1 {{problem verification failed}}
func @ssa_dep_violated(%a0 : i32, %a1 : i32, %a2 : i32) -> i32 attributes {
operatortypes = [
{ name = "_0", latency = 0 },
{ name = "_1", latency = 1 },
{ name = "_3", latency = 3 }
] } {
%0 = addi %a0, %a0 { opr = "_3", problemStartTime = 0 } : i32
%1 = addi %a1, %0 { opr = "_1", problemStartTime = 3 } : i32
%2 = addi %1, %a2 { opr = "_0", problemStartTime = 3 } : i32
return { opr = "_1", problemStartTime = 4 } %2 : i32
}
// -----
// expected-error@+2 {{Precedence violated for dependence}}
// expected-error@+1 {{problem verification failed}}
func @aux_dep_violated() attributes {
auxdeps = [ [0,1], [1,2], [2,3] ]
} {
%0 = constant { problemStartTime = 123 } 0 : i32
%1 = constant { problemStartTime = 456 } 1 : i32
%2 = constant { problemStartTime = 123 } 2 : i32
return { problemStartTime = 456 }
}

View File

@ -0,0 +1,66 @@
// RUN: circt-opt %s -test-scheduling-problem -allow-unregistered-dialect
// RUN: circt-opt %s -test-asap-scheduler -allow-unregistered-dialect | FileCheck %s -check-prefix=ASAP
// ASAP-LABEL: unit_latencies
func @unit_latencies(%a1 : i32, %a2 : i32, %a3 : i32, %a4 : i32) -> i32 {
// ASAP-NEXT: asapStartTime = 0
%0 = addi %a1, %a2 { problemStartTime = 0 } : i32
// ASAP-NEXT: asapStartTime = 1
%1 = addi %0, %a3 { problemStartTime = 1 } : i32
// ASAP-NEXT: asapStartTime = 2
%2:3 = "more.results"(%0, %1) { problemStartTime = 2 } : (i32, i32) -> (i32, i32, i32)
// ASAP-NEXT: asapStartTime = 3
%3 = addi %a4, %2#1 { problemStartTime = 3 } : i32
// ASAP-NEXT: asapStartTime = 3
%4 = addi %2#0, %2#2 { problemStartTime = 4 } : i32
// ASAP-NEXT: asapStartTime = 4
%5 = addi %3, %3 { problemStartTime = 4 } : i32
// ASAP-NEXT: asapStartTime = 5
%6 = "more.operands"(%3, %4, %5) { problemStartTime = 6 } : (i32, i32, i32) -> i32
// ASAP-NEXT: asapStartTime = 6
return { problemStartTime = 7 } %6 : i32
}
// ASAP-LABEL: arbitrary_latencies
func @arbitrary_latencies(%v : complex<f32>) -> f32 attributes {
operatortypes = [
{ name = "extr", latency = 0 },
{ name = "add", latency = 3 },
{ name = "mult", latency = 6 },
{ name = "sqrt", latency = 10 }
] } {
// ASAP-NEXT: asapStartTime = 0
%0 = "complex.re"(%v) { opr = "extr", problemStartTime = 0 } : (complex<f32>) -> f32
// ASAP-NEXT: asapStartTime = 0
%1 = "complex.im"(%v) { opr = "extr", problemStartTime = 10 } : (complex<f32>) -> f32
// ASAP-NEXT: asapStartTime = 0
%2 = mulf %0, %0 { opr = "mult", problemStartTime = 20 } : f32
// ASAP-NEXT: asapStartTime = 0
%3 = mulf %1, %1 { opr = "mult", problemStartTime = 30 } : f32
// ASAP-NEXT: asapStartTime = 6
%4 = addf %2, %3 { opr = "add", problemStartTime = 40 } : f32
// ASAP-NEXT: asapStartTime = 9
%5 = "math.sqrt"(%4) { opr = "sqrt", problemStartTime = 50 } : (f32) -> f32
// ASAP-NEXT: asapStartTime = 19
return { problemStartTime = 60 } %5 : f32
}
// ASAP-LABEL: auxiliary_dependences
func @auxiliary_dependences() attributes { auxdeps = [
[0,1], [0,2], [2,3], [3,4], [3,6], [4,5], [5,6]
] } {
// ASAP-NEXT: asapStartTime = 0
%0 = constant { problemStartTime = 0 } 0 : i32
// ASAP-NEXT: asapStartTime = 1
%1 = constant { problemStartTime = 1 } 1 : i32
// ASAP-NEXT: asapStartTime = 1
%2 = constant { problemStartTime = 2 } 2 : i32
// ASAP-NEXT: asapStartTime = 2
%3 = constant { problemStartTime = 3 } 3 : i32
// ASAP-NEXT: asapStartTime = 3
%4 = constant { problemStartTime = 4 } 4 : i32
// ASAP-NEXT: asapStartTime = 4
%5 = constant { problemStartTime = 5 } 5 : i32
// ASAP-NEXT: asapStartTime = 5
return { problemStartTime = 6 }
}

View File

@ -1,71 +0,0 @@
// RUN: circt-opt -test-asap-scheduler -verify-diagnostics -allow-unregistered-dialect %s
func @test_asap1(%a1 : i32, %a2 : i32, %a3 : i32, %a4 : i32) -> i32 {
// expected-remark@+1 {{start time = 0}}
%0 = addi %a1, %a2 : i32
// expected-remark@+1 {{start time = 1}}
%1 = addi %0, %a3 : i32
// expected-remark@+1 {{start time = 1}}
%2 = addi %a4, %0 : i32
// expected-remark@+1 {{start time = 2}}
%3 = addi %2, %1 : i32
// expected-remark@+1 {{start time = 3}}
%4 = addi %3, %3 : i32
// expected-remark@+1 {{start time = 4}}
%5 = "more.operands"(%0, %1, %2, %3, %4) : (i32, i32, i32, i32, i32) -> i32
// expected-remark@+1 {{start time = 5}}
return %5 : i32
}
func @test_asap2(%v : complex<f32>) -> f32 attributes { operatortypes = [
{ name = "extr", latency = 0 },
{ name = "add", latency = 3 },
{ name = "mult", latency = 6 },
{ name = "sqrt", latency = 10 }
] } {
// expected-remark@+1 {{start time = 0}}
%0 = "complex.re"(%v) { opr = "extr" } : (complex<f32>) -> f32
// expected-remark@+1 {{start time = 0}}
%1 = "complex.im"(%v) { opr = "extr" } : (complex<f32>) -> f32
// expected-remark@+1 {{start time = 0}}
%2 = mulf %0, %0 { opr = "mult" } : f32
// expected-remark@+1 {{start time = 0}}
%3 = mulf %1, %1 { opr = "mult" } : f32
// expected-remark@+1 {{start time = 6}}
%4 = addf %2, %3 { opr = "add" } : f32
// expected-remark@+1 {{start time = 9}}
%5 = "math.sqrt"(%4) { opr = "sqrt" } : (f32) -> f32
// expected-remark@+1 {{start time = 19}}
return %5 : f32
}
func @test_asap3() attributes { auxdeps = [
[0,1], [0,2], [2,3], [3,4], [3,6], [4,5], [5,6]
] } {
// expected-remark@+1 {{start time = 0}}
%0 = constant 0 : i32
// expected-remark@+1 {{start time = 1}}
%1 = constant 1 : i32
// expected-remark@+1 {{start time = 1}}
%2 = constant 2 : i32
// expected-remark@+1 {{start time = 2}}
%3 = constant 3 : i32
// expected-remark@+1 {{start time = 3}}
%4 = constant 4 : i32
// expected-remark@+1 {{start time = 4}}
%5 = constant 5 : i32
// expected-remark@+1 {{start time = 5}}
return
}
// expected-error@+2 {{dependence cycle detected}}
// expected-error@+1 {{scheduling failed}}
func @test_asap4() attributes { auxdeps = [
[0,1], [1,2], [2,3], [3,1]
] } {
%0 = constant 0 : i32
%1 = constant 1 : i32
%2 = constant 2 : i32
%3 = constant 3 : i32
return
}