mirror of https://github.com/llvm/circt.git
[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:
parent
dacf8aa2a4
commit
7d89311c07
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue