Skip to content

Commit 8bad5c7

Browse files
aartbikcommit-bot@chromium.org
authored andcommitted
[vm/compiler] Non-speculative X64 long division/remainder.
Rationale: Having a non-speculative implementation avoids deopting under JIT and enables AOT. #33967 Change-Id: Ib38200502f5a8f63912ff5d7a808ea6e27f9001d Reviewed-on: https://dart-review.googlesource.com/67502 Commit-Queue: Aart Bik <[email protected]> Reviewed-by: Alexander Markov <[email protected]>
1 parent 918cda1 commit 8bad5c7

File tree

10 files changed

+201
-48
lines changed

10 files changed

+201
-48
lines changed

runtime/vm/compiler/aot/aot_call_specializer.cc

+4
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,10 @@ bool AotCallSpecializer::TryOptimizeStaticCallUsingStaticTypes(
572572
case Token::kADD:
573573
case Token::kSUB:
574574
case Token::kMUL:
575+
#ifdef TARGET_ARCH_X64 // TODO(ajcbik): support all
576+
case Token::kMOD:
577+
case Token::kTRUNCDIV:
578+
#endif
575579
case Token::kDIV: {
576580
if ((op_kind == Token::kDIV) &&
577581
!FlowGraphCompiler::SupportsHardwareDivision()) {

runtime/vm/compiler/aot/precompiler.cc

+2
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,8 @@ static Dart_QualifiedFunctionName vm_entry_points[] = {
502502
{"dart:core", "CyclicInitializationError", "CyclicInitializationError."},
503503
{"dart:core", "FallThroughError", "FallThroughError._create"},
504504
{"dart:core", "FormatException", "FormatException."},
505+
{"dart:core", "IntegerDivisionByZeroException",
506+
"IntegerDivisionByZeroException."},
505507
{"dart:core", "NoSuchMethodError", "NoSuchMethodError._withType"},
506508
{"dart:core", "NullThrownError", "NullThrownError."},
507509
{"dart:core", "OutOfMemoryError", "OutOfMemoryError."},

runtime/vm/compiler/backend/il.h

+4
Original file line numberDiff line numberDiff line change
@@ -6386,6 +6386,10 @@ class BinaryInt64OpInstr : public BinaryIntegerOpInstr {
63866386
return false;
63876387
}
63886388

6389+
virtual bool MayThrow() const {
6390+
return op_kind() == Token::kMOD || op_kind() == Token::kTRUNCDIV;
6391+
}
6392+
63896393
virtual Representation representation() const { return kUnboxedInt64; }
63906394

63916395
virtual Representation RequiredInputRepresentation(intptr_t idx) const {

runtime/vm/compiler/backend/il_x64.cc

+136-10
Original file line numberDiff line numberDiff line change
@@ -5196,6 +5196,107 @@ void CheckArrayBoundInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
51965196
}
51975197
}
51985198

5199+
class Int64DivideSlowPath : public ThrowErrorSlowPathCode {
5200+
public:
5201+
static const intptr_t kNumberOfArguments = 0;
5202+
5203+
Int64DivideSlowPath(BinaryInt64OpInstr* instruction,
5204+
Register right,
5205+
intptr_t try_index)
5206+
: ThrowErrorSlowPathCode(instruction,
5207+
kIntegerDivisionByZeroExceptionRuntimeEntry,
5208+
kNumberOfArguments,
5209+
try_index),
5210+
is_mod_(instruction->op_kind() == Token::kMOD),
5211+
right_(right),
5212+
div_by_minus_one_label_(),
5213+
adjust_sign_label_() {}
5214+
5215+
void EmitNativeCode(FlowGraphCompiler* compiler) override {
5216+
// Main entry throws, use code of superclass.
5217+
ThrowErrorSlowPathCode::EmitNativeCode(compiler);
5218+
// Handle modulo/division by minus one.
5219+
__ Bind(div_by_minus_one_label());
5220+
if (is_mod_) {
5221+
__ xorq(RDX, RDX); // x % -1 = 0
5222+
} else {
5223+
__ negq(RAX); // x / -1 = -x
5224+
}
5225+
__ jmp(exit_label());
5226+
// Adjust modulo for negative sign.
5227+
// if (right < 0)
5228+
// out -= right;
5229+
// else
5230+
// out += right;
5231+
if (is_mod_) {
5232+
Label subtract;
5233+
__ Bind(adjust_sign_label());
5234+
__ testq(right_, right_);
5235+
__ j(LESS, &subtract, Assembler::kNearJump);
5236+
__ addq(RDX, right_);
5237+
__ jmp(exit_label());
5238+
__ Bind(&subtract);
5239+
__ subq(RDX, right_);
5240+
__ jmp(exit_label());
5241+
}
5242+
}
5243+
5244+
const char* name() override { return "int64 divide"; }
5245+
5246+
Label* div_by_minus_one_label() { return &div_by_minus_one_label_; }
5247+
Label* adjust_sign_label() { return &adjust_sign_label_; }
5248+
5249+
private:
5250+
bool is_mod_;
5251+
Register right_;
5252+
Label div_by_minus_one_label_;
5253+
Label adjust_sign_label_;
5254+
};
5255+
5256+
static void EmitInt64ModTruncDiv(FlowGraphCompiler* compiler,
5257+
BinaryInt64OpInstr* instruction,
5258+
Token::Kind op_kind,
5259+
Register left,
5260+
Register right,
5261+
Register tmp,
5262+
Register out) {
5263+
ASSERT(op_kind == Token::kMOD || op_kind == Token::kTRUNCDIV);
5264+
5265+
// Set up a slow path.
5266+
Int64DivideSlowPath* slow_path = new (Z)
5267+
Int64DivideSlowPath(instruction, right, compiler->CurrentTryIndex());
5268+
compiler->AddSlowPathCode(slow_path);
5269+
5270+
// Handle modulo/division by zero exception on slow path.
5271+
__ testq(right, right);
5272+
__ j(EQUAL, slow_path->entry_label());
5273+
5274+
// Handle modulo/division by minus one explicitly on slow path
5275+
// (to avoid arithmetic exception on 0x8000000000000000 / -1).
5276+
__ cmpq(right, Immediate(-1));
5277+
__ j(EQUAL, slow_path->div_by_minus_one_label());
5278+
5279+
// Perform actual operation
5280+
// out = left % right
5281+
// or
5282+
// out = left / right.
5283+
ASSERT(left == RAX);
5284+
__ cqo(); // sign-ext rax into rdx:rax
5285+
__ idivq(right); // quotient rax, remainder rdx
5286+
if (op_kind == Token::kMOD) {
5287+
ASSERT(out == RDX);
5288+
ASSERT(tmp == RAX);
5289+
// For the % operator, again the idiv instruction does
5290+
// not quite do what we want. Adjust for sign on slow path.
5291+
__ testq(out, out);
5292+
__ j(LESS, slow_path->adjust_sign_label());
5293+
} else {
5294+
ASSERT(out == RAX);
5295+
ASSERT(tmp == RDX);
5296+
}
5297+
__ Bind(slow_path->exit_label());
5298+
}
5299+
51995300
template <typename OperandType>
52005301
static void EmitInt64Arithmetic(FlowGraphCompiler* compiler,
52015302
Token::Kind op_kind,
@@ -5227,30 +5328,55 @@ static void EmitInt64Arithmetic(FlowGraphCompiler* compiler,
52275328

52285329
LocationSummary* BinaryInt64OpInstr::MakeLocationSummary(Zone* zone,
52295330
bool opt) const {
5230-
const intptr_t kNumInputs = 2;
5231-
const intptr_t kNumTemps = 0;
5232-
LocationSummary* summary = new (zone)
5233-
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
5234-
summary->set_in(0, Location::RequiresRegister());
5235-
summary->set_in(1, Location::RegisterOrConstant(right()));
5236-
summary->set_out(0, Location::SameAsFirstInput());
5237-
return summary;
5331+
switch (op_kind()) {
5332+
case Token::kMOD:
5333+
case Token::kTRUNCDIV: {
5334+
const intptr_t kNumInputs = 2;
5335+
const intptr_t kNumTemps = 1;
5336+
LocationSummary* summary = new (zone) LocationSummary(
5337+
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
5338+
summary->set_in(0, Location::RegisterLocation(RAX));
5339+
summary->set_in(1, Location::RequiresRegister());
5340+
// Intel uses rdx:rax with quotient rax and remainder rdx. Pick the
5341+
// appropriate one for output and reserve the other as temp.
5342+
summary->set_out(
5343+
0, Location::RegisterLocation(op_kind() == Token::kMOD ? RDX : RAX));
5344+
summary->set_temp(
5345+
0, Location::RegisterLocation(op_kind() == Token::kMOD ? RAX : RDX));
5346+
return summary;
5347+
}
5348+
default: {
5349+
const intptr_t kNumInputs = 2;
5350+
const intptr_t kNumTemps = 0;
5351+
LocationSummary* summary = new (zone) LocationSummary(
5352+
zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
5353+
summary->set_in(0, Location::RequiresRegister());
5354+
summary->set_in(1, Location::RegisterOrConstant(right()));
5355+
summary->set_out(0, Location::SameAsFirstInput());
5356+
return summary;
5357+
}
5358+
}
52385359
}
52395360

52405361
void BinaryInt64OpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
52415362
const Location left = locs()->in(0);
52425363
const Location right = locs()->in(1);
52435364
const Location out = locs()->out(0);
5244-
ASSERT(out.reg() == left.reg());
52455365
ASSERT(!can_overflow());
52465366
ASSERT(!CanDeoptimize());
52475367

5248-
if (right.IsConstant()) {
5368+
if (op_kind() == Token::kMOD || op_kind() == Token::kTRUNCDIV) {
5369+
const Location temp = locs()->temp(0);
5370+
EmitInt64ModTruncDiv(compiler, this, op_kind(), left.reg(), right.reg(),
5371+
temp.reg(), out.reg());
5372+
} else if (right.IsConstant()) {
5373+
ASSERT(out.reg() == left.reg());
52495374
ConstantInstr* constant_instr = right.constant_instruction();
52505375
const int64_t value =
52515376
constant_instr->GetUnboxedSignedIntegerConstantValue();
52525377
EmitInt64Arithmetic(compiler, op_kind(), left.reg(), Immediate(value));
52535378
} else {
5379+
ASSERT(out.reg() == left.reg());
52545380
EmitInt64Arithmetic(compiler, op_kind(), left.reg(), right.reg());
52555381
}
52565382
}

runtime/vm/exceptions.cc

+4
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,10 @@ RawObject* Exceptions::Create(ExceptionType type, const Array& arguments) {
928928
class_name = &Symbols::ArgumentError();
929929
constructor_name = &Symbols::DotValue();
930930
break;
931+
case kIntegerDivisionByZeroException:
932+
library = Library::CoreLibrary();
933+
class_name = &Symbols::IntegerDivisionByZeroException();
934+
break;
931935
case kNoSuchMethod:
932936
library = Library::CoreLibrary();
933937
class_name = &Symbols::NoSuchMethodError();

runtime/vm/exceptions.h

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Exceptions : AllStatic {
5252
kRangeMsg,
5353
kArgument,
5454
kArgumentValue,
55+
kIntegerDivisionByZeroException,
5556
kNoSuchMethod,
5657
kFormat,
5758
kUnsupported,

runtime/vm/runtime_entry.cc

+5
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ DEFINE_RUNTIME_ENTRY(ArgumentErrorUnboxedInt64, 0) {
225225
Exceptions::ThrowArgumentError(value);
226226
}
227227

228+
DEFINE_RUNTIME_ENTRY(IntegerDivisionByZeroException, 0) {
229+
const Array& args = Array::Handle(Array::New(0));
230+
Exceptions::ThrowByType(Exceptions::kIntegerDivisionByZeroException, args);
231+
}
232+
228233
static void EnsureNewOrRemembered(Isolate* isolate,
229234
Thread* thread,
230235
const Object& result) {

runtime/vm/runtime_entry_list.h

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace dart {
4040
V(RangeError) \
4141
V(NullError) \
4242
V(ArgumentErrorUnboxedInt64) \
43+
V(IntegerDivisionByZeroException) \
4344
V(ReThrow) \
4445
V(StackOverflow) \
4546
V(Throw) \

runtime/vm/symbols.h

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class ObjectPointerVisitor;
7373
V(FallThroughError, "FallThroughError") \
7474
V(AbstractClassInstantiationError, "AbstractClassInstantiationError") \
7575
V(NoSuchMethodError, "NoSuchMethodError") \
76+
V(IntegerDivisionByZeroException, "IntegerDivisionByZeroException") \
7677
V(CyclicInitializationError, "CyclicInitializationError") \
7778
V(_CompileTimeError, "_CompileTimeError") \
7879
V(ThrowNew, "_throwNew") \

tests/language_2/vm/modtruncdiv_int_test.dart

+43-38
Original file line numberDiff line numberDiff line change
@@ -159,56 +159,61 @@ doTruncDivVars(int xlo, int xhi, int ylo, int yhi) {
159159
}
160160

161161
main() {
162-
// Repeat constant tests to enter JIT (when applicable).
163-
162+
// Repeat to enter JIT (when applicable).
164163
for (int i = 0; i < 20; i++) {
164+
// Constants.
165+
165166
doModConstants();
166167
doTruncDivConstants();
167-
}
168168

169-
// Variable ranges.
169+
// Variable ranges.
170170

171-
acc = 0;
172-
doModVars(3, 5, 2, 6);
173-
Expect.equals(28, acc);
171+
acc = 0;
172+
doModVars(3, 5, 2, 6);
173+
Expect.equals(28, acc);
174174

175-
acc = 0;
176-
doModVars((3 << 32) - 1, (3 << 32) + 1, (3 << 32) - 1, (3 << 32) + 1);
177-
Expect.equals(38654705666, acc);
175+
acc = 0;
176+
doModVars((3 << 32) - 1, (3 << 32) + 1, (3 << 32) - 1, (3 << 32) + 1);
177+
Expect.equals(38654705666, acc);
178178

179-
acc = 0;
180-
doTruncDivVars(3, 5, 2, 6);
181-
Expect.equals(11, acc);
179+
acc = 0;
180+
doTruncDivVars(3, 5, 2, 6);
181+
Expect.equals(11, acc);
182182

183-
acc = 0;
184-
doTruncDivVars(-5, -3, 2, 6);
185-
Expect.equals(-11, acc);
183+
acc = 0;
184+
doTruncDivVars(-5, -3, 2, 6);
185+
Expect.equals(-11, acc);
186186

187-
acc = 0;
188-
doTruncDivVars(3, 5, -6, -2);
189-
Expect.equals(-11, acc);
187+
acc = 0;
188+
doTruncDivVars(3, 5, -6, -2);
189+
Expect.equals(-11, acc);
190190

191-
acc = 0;
192-
doTruncDivVars(-5, -3, -6, -2);
193-
Expect.equals(11, acc);
191+
acc = 0;
192+
doTruncDivVars(-5, -3, -6, -2);
193+
Expect.equals(11, acc);
194194

195-
acc = 0;
196-
doTruncDivVars((3 << 32) - 1, (3 << 32) + 1, 3, 6);
197-
Expect.equals(36721970376, acc);
195+
acc = 0;
196+
doTruncDivVars((3 << 32) - 1, (3 << 32) + 1, 3, 6);
197+
Expect.equals(36721970376, acc);
198198

199-
// Exceptions at the right time.
199+
acc = 0;
200+
doTruncDivVars(minInt64, minInt64, -1, -1);
201+
Expect.equals(minInt64, acc);
200202

201-
acc = 0;
202-
try {
203-
doModVars(9, 9, -9, 0);
204-
acc = 0; // don't reach!
205-
} catch (e, s) {}
206-
Expect.equals(12, acc);
203+
// Exceptions at the right time.
207204

208-
acc = 0;
209-
try {
210-
doTruncDivVars(9, 9, -9, 0);
211-
acc = 0; // don't reach!
212-
} catch (e, s) {}
213-
Expect.equals(-23, acc);
205+
acc = 0;
206+
try {
207+
doModVars(9, 9, -9, 0);
208+
acc = 0; // don't reach!
209+
} on IntegerDivisionByZeroException catch (e, s) {}
210+
Expect.equals(12, acc);
211+
212+
acc = 0;
213+
try {
214+
doTruncDivVars(9, 9, -9, 0);
215+
acc = 0; // don't reach!
216+
} on IntegerDivisionByZeroException catch (e, s) {}
217+
Expect.equals(-23, acc);
218+
}
214219
}

0 commit comments

Comments
 (0)