Diff
Modified: trunk/Source/_javascript_Core/ChangeLog (203995 => 203996)
--- trunk/Source/_javascript_Core/ChangeLog 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/ChangeLog 2016-08-01 23:22:15 UTC (rev 203996)
@@ -1,3 +1,53 @@
+2016-07-22 Filip Pizlo <[email protected]>
+
+ [B3] Fusing immediates into test instructions should work again
+ https://bugs.webkit.org/show_bug.cgi?id=160073
+
+ Reviewed by Sam Weinig.
+
+ When we introduced BitImm, we forgot to change the Branch(BitAnd(value, constant))
+ fusion. This emits test instructions, so it should use BitImm for the constant. But it
+ was still using Imm! This meant that isValidForm() always returned false.
+
+ This fixes the code path to use BitImm, and turns off our use of BitImm64 on x86 since
+ it provides no benefit on x86 and has some risk (the code appears to play fast and loose
+ with the scratch register).
+
+ This is not an obvious progression on anything, so I added comprehensive tests to
+ testb3, which check that we selected the optimal instruction in a variety of situations.
+ We should add more tests like this!
+
+ Rolling this back in after fixing ARM64. The bug was that branchTest32|64 on ARM64 doesn't
+ actually support BitImm or BitImm64, at least not yet. Disabling that in AirOpcodes makes
+ this patch not a regression on ARM64. That change was reviewed by Benjamin Poulain.
+
+ * b3/B3BasicBlock.h:
+ (JSC::B3::BasicBlock::successorBlock):
+ * b3/B3LowerToAir.cpp:
+ (JSC::B3::Air::LowerToAir::createGenericCompare):
+ * b3/B3LowerToAir.h:
+ * b3/air/AirArg.cpp:
+ (JSC::B3::Air::Arg::isRepresentableAs):
+ (JSC::B3::Air::Arg::usesTmp):
+ * b3/air/AirArg.h:
+ (JSC::B3::Air::Arg::isRepresentableAs):
+ (JSC::B3::Air::Arg::castToType):
+ (JSC::B3::Air::Arg::asNumber):
+ * b3/air/AirCode.h:
+ (JSC::B3::Air::Code::size):
+ (JSC::B3::Air::Code::at):
+ * b3/air/AirOpcode.opcodes:
+ * b3/air/AirValidate.h:
+ * b3/air/opcode_generator.rb:
+ * b3/testb3.cpp:
+ (JSC::B3::compile):
+ (JSC::B3::compileAndRun):
+ (JSC::B3::lowerToAirForTesting):
+ (JSC::B3::testSomeEarlyRegister):
+ (JSC::B3::testBranchBitAndImmFusion):
+ (JSC::B3::zero):
+ (JSC::B3::run):
+
2016-08-01 Filip Pizlo <[email protected]>
Rationalize varargs stack overflow checks
Modified: trunk/Source/_javascript_Core/b3/B3LowerToAir.cpp (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/B3LowerToAir.cpp 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/B3LowerToAir.cpp 2016-08-01 23:22:15 UTC (rev 203996)
@@ -1330,22 +1330,43 @@
Value* left = value->child(0);
Value* right = value->child(1);
- // FIXME: We don't actually have to worry about leftImm.
- // https://bugs.webkit.org/show_bug.cgi?id=150954
+ bool hasRightConst;
+ int64_t rightConst;
+ Arg rightImm;
+ Arg rightImm64;
- Arg leftImm = imm(left);
- Arg rightImm = imm(right);
+ hasRightConst = right->hasInt();
+ if (hasRightConst) {
+ rightConst = right->asInt();
+ rightImm = bitImm(right);
+ rightImm64 = bitImm64(right);
+ }
- auto tryTestLoadImm = [&] (Arg::Width width, B3::Opcode loadOpcode) -> Inst {
- if (rightImm && rightImm.isRepresentableAs(width, Arg::Unsigned)) {
+ auto tryTestLoadImm = [&] (Arg::Width width, Arg::Signedness signedness, B3::Opcode loadOpcode) -> Inst {
+ if (!hasRightConst)
+ return Inst();
+ // Signed loads will create high bits, so if the immediate has high bits
+ // then we cannot proceed. Consider BitAnd(Load8S(ptr), 0x101). This cannot
+ // be turned into testb (ptr), $1, since if the high bit within that byte
+ // was set then it would be extended to include 0x100. The handling below
+ // won't anticipate this, so we need to catch it here.
+ if (signedness == Arg::Signed
+ && !Arg::isRepresentableAs(width, Arg::Unsigned, rightConst))
+ return Inst();
+
+ // FIXME: If this is unsigned then we can chop things off of the immediate.
+ // This might make the immediate more legal. Perhaps that's a job for
+ // strength reduction?
+
+ if (rightImm) {
if (Inst result = tryTest(width, loadPromise(left, loadOpcode), rightImm)) {
commitInternal(left);
return result;
}
}
- if (leftImm && leftImm.isRepresentableAs(width, Arg::Unsigned)) {
- if (Inst result = tryTest(width, leftImm, loadPromise(right, loadOpcode))) {
- commitInternal(right);
+ if (rightImm64) {
+ if (Inst result = tryTest(width, loadPromise(left, loadOpcode), rightImm64)) {
+ commitInternal(left);
return result;
}
}
@@ -1355,24 +1376,28 @@
if (canCommitInternal) {
// First handle test's that involve fewer bits than B3's type system supports.
- if (Inst result = tryTestLoadImm(Arg::Width8, Load8Z))
+ if (Inst result = tryTestLoadImm(Arg::Width8, Arg::Unsigned, Load8Z))
return result;
- if (Inst result = tryTestLoadImm(Arg::Width8, Load8S))
+ if (Inst result = tryTestLoadImm(Arg::Width8, Arg::Signed, Load8S))
return result;
- if (Inst result = tryTestLoadImm(Arg::Width16, Load16Z))
+ if (Inst result = tryTestLoadImm(Arg::Width16, Arg::Unsigned, Load16Z))
return result;
- if (Inst result = tryTestLoadImm(Arg::Width16, Load16S))
+ if (Inst result = tryTestLoadImm(Arg::Width16, Arg::Signed, Load16S))
return result;
- // Now handle test's that involve a load and an immediate. Note that immediates
- // are 32-bit, and we want zero-extension. Hence, the immediate form is compiled
- // as a 32-bit test. Note that this spits on the grave of inferior endians, such
- // as the big one.
+ // This allows us to use a 32-bit test for 64-bit BitAnd if the immediate is
+ // representable as an unsigned 32-bit value. The logic involved is the same
+ // as if we were pondering using a 32-bit test for
+ // BitAnd(SExt(Load(ptr)), const), in the sense that in both cases we have
+ // to worry about high bits. So, we use the "Signed" version of this helper.
+ if (Inst result = tryTestLoadImm(Arg::Width32, Arg::Signed, Load))
+ return result;
- if (Inst result = tryTestLoadImm(Arg::Width32, Load))
+ // This is needed to handle 32-bit test for arbitrary 32-bit immediates.
+ if (Inst result = tryTestLoadImm(width, Arg::Unsigned, Load))
return result;
// Now handle test's that involve a load.
@@ -1391,30 +1416,23 @@
// Now handle test's that involve an immediate and a tmp.
- if (leftImm) {
- if ((width == Arg::Width32 && leftImm.value() == 0xffffffff)
- || (width == Arg::Width64 && leftImm.value() == -1)) {
- ArgPromise argPromise = tmpPromise(right);
- if (Inst result = tryTest(width, argPromise, argPromise))
- return result;
- }
- if (leftImm.isRepresentableAs<uint32_t>()) {
- if (Inst result = tryTest(Arg::Width32, leftImm, tmpPromise(right)))
- return result;
- }
- }
-
- if (rightImm) {
- if ((width == Arg::Width32 && rightImm.value() == 0xffffffff)
- || (width == Arg::Width64 && rightImm.value() == -1)) {
+ if (hasRightConst) {
+ if ((width == Arg::Width32 && rightConst == 0xffffffff)
+ || (width == Arg::Width64 && rightConst == -1)) {
ArgPromise argPromise = tmpPromise(left);
if (Inst result = tryTest(width, argPromise, argPromise))
return result;
}
- if (rightImm.isRepresentableAs<uint32_t>()) {
+ if (isRepresentableAs<uint32_t>(rightConst)) {
if (Inst result = tryTest(Arg::Width32, tmpPromise(left), rightImm))
return result;
+ if (Inst result = tryTest(Arg::Width32, tmpPromise(left), rightImm64))
+ return result;
}
+ if (Inst result = tryTest(width, tmpPromise(left), rightImm))
+ return result;
+ if (Inst result = tryTest(width, tmpPromise(left), rightImm64))
+ return result;
}
// Finally, just do tmp's.
@@ -1432,37 +1450,37 @@
}
}
- if (Arg::isValidImmForm(-1)) {
+ if (Arg::isValidBitImmForm(-1)) {
if (canCommitInternal && value->as<MemoryValue>()) {
// Handle things like Branch(Load8Z(value))
- if (Inst result = tryTest(Arg::Width8, loadPromise(value, Load8Z), Arg::imm(-1))) {
+ if (Inst result = tryTest(Arg::Width8, loadPromise(value, Load8Z), Arg::bitImm(-1))) {
commitInternal(value);
return result;
}
- if (Inst result = tryTest(Arg::Width8, loadPromise(value, Load8S), Arg::imm(-1))) {
+ if (Inst result = tryTest(Arg::Width8, loadPromise(value, Load8S), Arg::bitImm(-1))) {
commitInternal(value);
return result;
}
- if (Inst result = tryTest(Arg::Width16, loadPromise(value, Load16Z), Arg::imm(-1))) {
+ if (Inst result = tryTest(Arg::Width16, loadPromise(value, Load16Z), Arg::bitImm(-1))) {
commitInternal(value);
return result;
}
- if (Inst result = tryTest(Arg::Width16, loadPromise(value, Load16S), Arg::imm(-1))) {
+ if (Inst result = tryTest(Arg::Width16, loadPromise(value, Load16S), Arg::bitImm(-1))) {
commitInternal(value);
return result;
}
- if (Inst result = tryTest(width, loadPromise(value), Arg::imm(-1))) {
+ if (Inst result = tryTest(width, loadPromise(value), Arg::bitImm(-1))) {
commitInternal(value);
return result;
}
}
- if (Inst result = test(width, resCond, tmpPromise(value), Arg::imm(-1)))
+ if (Inst result = test(width, resCond, tmpPromise(value), Arg::bitImm(-1)))
return result;
}
Modified: trunk/Source/_javascript_Core/b3/B3LowerToAir.h (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/B3LowerToAir.h 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/B3LowerToAir.h 2016-08-01 23:22:15 UTC (rev 203996)
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -35,7 +35,7 @@
// This lowers the current B3 procedure to an Air code.
-void lowerToAir(Procedure&);
+JS_EXPORT_PRIVATE void lowerToAir(Procedure&);
} } // namespace JSC::B3
Modified: trunk/Source/_javascript_Core/b3/air/AirArg.cpp (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/air/AirArg.cpp 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/air/AirArg.cpp 2016-08-01 23:22:15 UTC (rev 203996)
@@ -57,31 +57,7 @@
bool Arg::isRepresentableAs(Width width, Signedness signedness) const
{
- switch (signedness) {
- case Signed:
- switch (width) {
- case Width8:
- return isRepresentableAs<int8_t>();
- case Width16:
- return isRepresentableAs<int16_t>();
- case Width32:
- return isRepresentableAs<int32_t>();
- case Width64:
- return isRepresentableAs<int64_t>();
- }
- case Unsigned:
- switch (width) {
- case Width8:
- return isRepresentableAs<uint8_t>();
- case Width16:
- return isRepresentableAs<uint16_t>();
- case Width32:
- return isRepresentableAs<uint32_t>();
- case Width64:
- return isRepresentableAs<uint64_t>();
- }
- }
- ASSERT_NOT_REACHED();
+ return isRepresentableAs(width, signedness, value());
}
bool Arg::usesTmp(Air::Tmp tmp) const
Modified: trunk/Source/_javascript_Core/b3/air/AirArg.h (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/air/AirArg.h 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/air/AirArg.h 2016-08-01 23:22:15 UTC (rev 203996)
@@ -793,8 +793,66 @@
{
return B3::isRepresentableAs<T>(value());
}
+
+ static bool isRepresentableAs(Width width, Signedness signedness, int64_t value)
+ {
+ switch (signedness) {
+ case Signed:
+ switch (width) {
+ case Width8:
+ return B3::isRepresentableAs<int8_t>(value);
+ case Width16:
+ return B3::isRepresentableAs<int16_t>(value);
+ case Width32:
+ return B3::isRepresentableAs<int32_t>(value);
+ case Width64:
+ return B3::isRepresentableAs<int64_t>(value);
+ }
+ case Unsigned:
+ switch (width) {
+ case Width8:
+ return B3::isRepresentableAs<uint8_t>(value);
+ case Width16:
+ return B3::isRepresentableAs<uint16_t>(value);
+ case Width32:
+ return B3::isRepresentableAs<uint32_t>(value);
+ case Width64:
+ return B3::isRepresentableAs<uint64_t>(value);
+ }
+ }
+ ASSERT_NOT_REACHED();
+ }
bool isRepresentableAs(Width, Signedness) const;
+
+ static int64_t castToType(Width width, Signedness signedness, int64_t value)
+ {
+ switch (signedness) {
+ case Signed:
+ switch (width) {
+ case Width8:
+ return static_cast<int8_t>(value);
+ case Width16:
+ return static_cast<int16_t>(value);
+ case Width32:
+ return static_cast<int32_t>(value);
+ case Width64:
+ return static_cast<int64_t>(value);
+ }
+ case Unsigned:
+ switch (width) {
+ case Width8:
+ return static_cast<uint8_t>(value);
+ case Width16:
+ return static_cast<uint16_t>(value);
+ case Width32:
+ return static_cast<uint32_t>(value);
+ case Width64:
+ return static_cast<uint64_t>(value);
+ }
+ }
+ ASSERT_NOT_REACHED();
+ }
template<typename T>
T asNumber() const
@@ -1300,11 +1358,11 @@
namespace WTF {
-void printInternal(PrintStream&, JSC::B3::Air::Arg::Kind);
-void printInternal(PrintStream&, JSC::B3::Air::Arg::Role);
-void printInternal(PrintStream&, JSC::B3::Air::Arg::Type);
-void printInternal(PrintStream&, JSC::B3::Air::Arg::Width);
-void printInternal(PrintStream&, JSC::B3::Air::Arg::Signedness);
+JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Kind);
+JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Role);
+JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Type);
+JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Width);
+JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Signedness);
template<typename T> struct DefaultHash;
template<> struct DefaultHash<JSC::B3::Air::Arg> {
Modified: trunk/Source/_javascript_Core/b3/air/AirCode.h (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/air/AirCode.h 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/air/AirCode.h 2016-08-01 23:22:15 UTC (rev 203996)
@@ -152,7 +152,7 @@
// Recomputes predecessors and deletes unreachable blocks.
void resetReachability();
- void dump(PrintStream&) const;
+ JS_EXPORT_PRIVATE void dump(PrintStream&) const;
unsigned size() const { return m_blocks.size(); }
BasicBlock* at(unsigned index) const { return m_blocks[index].get(); }
Modified: trunk/Source/_javascript_Core/b3/air/AirOpcode.opcodes (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/air/AirOpcode.opcodes 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/air/AirOpcode.opcodes 2016-08-01 23:22:15 UTC (rev 203996)
@@ -675,23 +675,23 @@
x86: RelCond, Index, Tmp
BranchTest8 U:G:32, U:G:8, U:G:8 /branch
- x86: ResCond, Addr, Imm
- x86: ResCond, Index, Imm
+ x86: ResCond, Addr, BitImm
+ x86: ResCond, Index, BitImm
BranchTest32 U:G:32, U:G:32, U:G:32 /branch
ResCond, Tmp, Tmp
- ResCond, Tmp, BitImm
- x86: ResCond, Addr, Imm
- x86: ResCond, Index, Imm
+ x86: ResCond, Tmp, BitImm
+ x86: ResCond, Addr, BitImm
+ x86: ResCond, Index, BitImm
# Warning: forms that take an immediate will sign-extend their immediate. You probably want
# BranchTest32 in most cases where you use an immediate.
64: BranchTest64 U:G:32, U:G:64, U:G:64 /branch
ResCond, Tmp, Tmp
- ResCond, Tmp, BitImm64
- x86: ResCond, Addr, Imm
+ x86: ResCond, Tmp, BitImm
+ x86: ResCond, Addr, BitImm
x86: ResCond, Addr, Tmp
- x86: ResCond, Index, Imm
+ x86: ResCond, Index, BitImm
BranchDouble U:G:32, U:F:64, U:F:64 /branch
DoubleCond, Tmp, Tmp
Modified: trunk/Source/_javascript_Core/b3/air/AirValidate.h (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/air/AirValidate.h 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/air/AirValidate.h 2016-08-01 23:22:15 UTC (rev 203996)
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -32,7 +32,7 @@
class Code;
-void validate(Code&, const char* dumpBefore = nullptr);
+JS_EXPORT_PRIVATE void validate(Code&, const char* dumpBefore = nullptr);
} } } // namespace JSC::B3::Air
Modified: trunk/Source/_javascript_Core/b3/air/opcode_generator.rb (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/air/opcode_generator.rb 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/air/opcode_generator.rb 2016-08-01 23:22:15 UTC (rev 203996)
@@ -490,7 +490,7 @@
outp.puts "namespace WTF {"
outp.puts "class PrintStream;"
- outp.puts "void printInternal(PrintStream&, JSC::B3::Air::Opcode);"
+ outp.puts "JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Opcode);"
outp.puts "} // namespace WTF"
}
Modified: trunk/Source/_javascript_Core/b3/testb3.cpp (203995 => 203996)
--- trunk/Source/_javascript_Core/b3/testb3.cpp 2016-08-01 23:21:48 UTC (rev 203995)
+++ trunk/Source/_javascript_Core/b3/testb3.cpp 2016-08-01 23:22:15 UTC (rev 203996)
@@ -135,6 +135,22 @@
return invoke<T>(*compile(procedure), arguments...);
}
+void lowerToAirForTesting(Procedure& proc)
+{
+ proc.resetReachability();
+
+ if (shouldBeVerbose())
+ dataLog("B3 before lowering:\n", proc);
+
+ validate(proc);
+ lowerToAir(proc);
+
+ if (shouldBeVerbose())
+ dataLog("Air after lowering:\n", proc.code());
+
+ Air::validate(proc.code());
+}
+
template<typename Type>
struct Operand {
const char* name;
@@ -12179,6 +12195,7 @@
polyJump->setGenerator(
[&] (CCallHelpers& jit, const StackmapGenerationParams& params) {
+ AllowMacroScratchRegisterUsage allowScratch(jit);
Vector<Box<CCallHelpers::Label>> labels = params.successorLabels();
MacroAssemblerCodePtr* jumpTable = bitwise_cast<MacroAssemblerCodePtr*>(
@@ -12811,6 +12828,48 @@
run(false);
}
+void testBranchBitAndImmFusion(
+ B3::Opcode valueModifier, Type valueType, int64_t constant,
+ Air::Opcode expectedOpcode, Air::Arg::Kind firstKind)
+{
+ // Currently this test should pass on all CPUs. But some CPUs may not support this fused
+ // instruction. It's OK to skip this test on those CPUs.
+
+ Procedure proc;
+
+ BasicBlock* root = proc.addBlock();
+ BasicBlock* _one_ = proc.addBlock();
+ BasicBlock* two = proc.addBlock();
+
+ Value* left = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0);
+
+ if (valueModifier != Identity) {
+ if (MemoryValue::accepts(valueModifier))
+ left = root->appendNew<MemoryValue>(proc, valueModifier, valueType, Origin(), left);
+ else
+ left = root->appendNew<Value>(proc, valueModifier, valueType, Origin(), left);
+ }
+
+ root->appendNew<Value>(
+ proc, Branch, Origin(),
+ root->appendNew<Value>(
+ proc, BitAnd, Origin(), left,
+ root->appendIntConstant(proc, Origin(), valueType, constant)));
+ root->setSuccessors(FrequentedBlock(one), FrequentedBlock(two));
+
+ one->appendNew<Value>(proc, Oops, Origin());
+ two->appendNew<Value>(proc, Oops, Origin());
+
+ lowerToAirForTesting(proc);
+
+ // The first basic block must end in a BranchTest64(resCond, tmp, bitImm).
+ Air::Inst terminal = proc.code()[0]->last();
+ CHECK_EQ(terminal.opcode, expectedOpcode);
+ CHECK_EQ(terminal.args[0].kind(), Air::Arg::ResCond);
+ CHECK_EQ(terminal.args[1].kind(), firstKind);
+ CHECK(terminal.args[2].kind() == Air::Arg::BitImm || terminal.args[2].kind() == Air::Arg::BitImm64);
+}
+
// Make sure the compiler does not try to optimize anything out.
NEVER_INLINE double zero()
{
@@ -14224,6 +14283,17 @@
RUN(testSomeEarlyRegister());
+ if (isX86()) {
+ RUN(testBranchBitAndImmFusion(Identity, Int64, 1, Air::BranchTest32, Air::Arg::Tmp));
+ RUN(testBranchBitAndImmFusion(Identity, Int64, 0xff, Air::BranchTest32, Air::Arg::Tmp));
+ RUN(testBranchBitAndImmFusion(Trunc, Int32, 1, Air::BranchTest32, Air::Arg::Tmp));
+ RUN(testBranchBitAndImmFusion(Trunc, Int32, 0xff, Air::BranchTest32, Air::Arg::Tmp));
+ RUN(testBranchBitAndImmFusion(Load8S, Int32, 1, Air::BranchTest8, Air::Arg::Addr));
+ RUN(testBranchBitAndImmFusion(Load8Z, Int32, 1, Air::BranchTest8, Air::Arg::Addr));
+ RUN(testBranchBitAndImmFusion(Load, Int32, 1, Air::BranchTest32, Air::Arg::Addr));
+ RUN(testBranchBitAndImmFusion(Load, Int64, 1, Air::BranchTest32, Air::Arg::Addr));
+ }
+
if (tasks.isEmpty())
usage();