Reland "[vm] MemoryCopy instruction for copying between typed data and strings."

This is a reland of 6ecd8a10ea

Original change's description:
> [vm] MemoryCopy instruction for copying between typed data and strings.
> 
> Used for copying the bytes from the Uint8List to the _OneByteString in
> String.fromCharCodes and the pure-ASCII case of UTF-8 decoding.
> 
> Issue https://github.com/dart-lang/sdk/issues/42072
> Closes https://github.com/dart-lang/sdk/issues/41703
> 
> Change-Id: I1ae300222877d1c6e64e32c2f40b8fb187a584c0
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/149500
> Commit-Queue: Aske Simon Christensen <askesc@google.com>
> Reviewed-by: Martin Kustermann <kustermann@google.com>

Change-Id: Ia231c521e5f2db168cfc6094dfc7322327dedc6d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150925
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Aske Simon Christensen 2020-06-11 12:29:19 +00:00 committed by commit-bot@chromium.org
parent 363ac11c1f
commit e35ca30ca5
27 changed files with 938 additions and 55 deletions

View file

@ -309,6 +309,19 @@ void Assembler::rep_movsb() {
EmitUint8(0xA4);
}
void Assembler::rep_movsw() {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xF3);
EmitUint8(0x66);
EmitUint8(0xA5);
}
void Assembler::rep_movsl() {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xF3);
EmitUint8(0xA5);
}
void Assembler::movss(XmmRegister dst, const Address& src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xF3);

View file

@ -298,6 +298,8 @@ class Assembler : public AssemblerBase {
void cmovlessl(Register dst, Register src);
void rep_movsb();
void rep_movsw();
void rep_movsl();
void movss(XmmRegister dst, const Address& src);
void movss(const Address& dst, XmmRegister src);

View file

@ -4755,14 +4755,16 @@ ASSEMBLER_TEST_GENERATE(TestRepMovsBytes, assembler) {
}
ASSEMBLER_TEST_RUN(TestRepMovsBytes, test) {
const char* from = "0123456789";
const char* to = new char[10];
typedef void (*TestRepMovsBytes)(const char* from, const char* to, int count);
const char* from = "0123456789x";
char* to = new char[11];
to[10] = 'y';
typedef void (*TestRepMovsBytes)(const char* from, char* to, int count);
reinterpret_cast<TestRepMovsBytes>(test->entry())(from, to, 10);
EXPECT_EQ(to[0], '0');
for (int i = 0; i < 10; i++) {
EXPECT_EQ(from[i], to[i]);
}
EXPECT_EQ(to[10], 'y');
delete[] to;
EXPECT_DISASSEMBLY(
"push esi\n"
@ -4778,6 +4780,93 @@ ASSEMBLER_TEST_RUN(TestRepMovsBytes, test) {
"ret\n");
}
ASSEMBLER_TEST_GENERATE(TestRepMovsWords, assembler) {
// Preserve registers.
__ pushl(ESI);
__ pushl(EDI);
__ pushl(ECX);
__ movl(ESI, Address(ESP, 4 * target::kWordSize)); // from.
__ movl(EDI, Address(ESP, 5 * target::kWordSize)); // to.
__ movl(ECX, Address(ESP, 6 * target::kWordSize)); // count.
__ rep_movsw();
__ popl(ECX);
__ popl(EDI);
__ popl(ESI);
__ ret();
}
ASSEMBLER_TEST_RUN(TestRepMovsWords, test) {
const uint16_t from[11] = {0x0123, 0x1234, 0x2345, 0x3456, 0x4567, 0x5678,
0x6789, 0x789A, 0x89AB, 0x9ABC, 0xABCD};
uint16_t* to = new uint16_t[11];
to[10] = 0xFEFE;
typedef void (*TestRepMovsWords)(const uint16_t* from, uint16_t* to,
int count);
reinterpret_cast<TestRepMovsWords>(test->entry())(from, to, 10);
EXPECT_EQ(to[0], 0x0123u);
for (int i = 0; i < 10; i++) {
EXPECT_EQ(from[i], to[i]);
}
EXPECT_EQ(to[10], 0xFEFEu);
delete[] to;
EXPECT_DISASSEMBLY(
"push esi\n"
"push edi\n"
"push ecx\n"
"mov esi,[esp+0x10]\n"
"mov edi,[esp+0x14]\n"
"mov ecx,[esp+0x18]\n"
"rep movsw\n"
"pop ecx\n"
"pop edi\n"
"pop esi\n"
"ret\n");
}
ASSEMBLER_TEST_GENERATE(TestRepMovsDwords, assembler) {
// Preserve registers.
__ pushl(ESI);
__ pushl(EDI);
__ pushl(ECX);
__ movl(ESI, Address(ESP, 4 * target::kWordSize)); // from.
__ movl(EDI, Address(ESP, 5 * target::kWordSize)); // to.
__ movl(ECX, Address(ESP, 6 * target::kWordSize)); // count.
__ rep_movsl();
__ popl(ECX);
__ popl(EDI);
__ popl(ESI);
__ ret();
}
ASSEMBLER_TEST_RUN(TestRepMovsDwords, test) {
const uint32_t from[11] = {0x01234567, 0x12345678, 0x23456789, 0x3456789A,
0x456789AB, 0x56789ABC, 0x6789ABCD, 0x789ABCDE,
0x89ABCDEF, 0x9ABCDEF0, 0xABCDEF01};
uint32_t* to = new uint32_t[11];
to[10] = 0xFEFEFEFE;
typedef void (*TestRepMovsDwords)(const uint32_t* from, uint32_t* to,
int count);
reinterpret_cast<TestRepMovsDwords>(test->entry())(from, to, 10);
EXPECT_EQ(to[0], 0x01234567u);
for (int i = 0; i < 10; i++) {
EXPECT_EQ(from[i], to[i]);
}
EXPECT_EQ(to[10], 0xFEFEFEFEu);
delete[] to;
EXPECT_DISASSEMBLY(
"push esi\n"
"push edi\n"
"push ecx\n"
"mov esi,[esp+0x10]\n"
"mov edi,[esp+0x14]\n"
"mov ecx,[esp+0x18]\n"
"rep movsl\n"
"pop ecx\n"
"pop edi\n"
"pop esi\n"
"ret\n");
}
// Called from assembler_test.cc.
ASSEMBLER_TEST_GENERATE(StoreIntoObject, assembler) {
__ pushl(THR);

View file

@ -384,11 +384,14 @@ void Assembler::movq(const Address& dst, const Immediate& imm) {
}
}
void Assembler::EmitSimple(int opcode, int opcode2) {
void Assembler::EmitSimple(int opcode, int opcode2, int opcode3) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(opcode);
if (opcode2 != -1) {
EmitUint8(opcode2);
if (opcode3 != -1) {
EmitUint8(opcode3);
}
}
}

View file

@ -391,6 +391,9 @@ class Assembler : public AssemblerBase {
SIMPLE(fsin, 0xD9, 0xFE)
SIMPLE(lock, 0xF0)
SIMPLE(rep_movsb, 0xF3, 0xA4)
SIMPLE(rep_movsw, 0xF3, 0x66, 0xA5)
SIMPLE(rep_movsl, 0xF3, 0xA5)
SIMPLE(rep_movsq, 0xF3, 0x48, 0xA5)
#undef SIMPLE
// XmmRegister operations with another register or an address.
#define XX(width, name, ...) \
@ -1030,7 +1033,7 @@ class Assembler : public AssemblerBase {
const Address& dst,
const Immediate& imm);
void EmitSimple(int opcode, int opcode2 = -1);
void EmitSimple(int opcode, int opcode2 = -1, int opcode3 = -1);
void EmitUnaryQ(Register reg, int opcode, int modrm_code);
void EmitUnaryL(Register reg, int opcode, int modrm_code);
void EmitUnaryQ(const Address& address, int opcode, int modrm_code);

View file

@ -5556,14 +5556,16 @@ ASSEMBLER_TEST_GENERATE(TestRepMovsBytes, assembler) {
}
ASSEMBLER_TEST_RUN(TestRepMovsBytes, test) {
const char* from = "0123456789";
const char* to = new char[10];
typedef void (*TestRepMovsBytes)(const char* from, const char* to, int count);
const char* from = "0123456789x";
char* to = new char[11];
to[10] = 'y';
typedef void (*TestRepMovsBytes)(const char* from, char* to, int count);
reinterpret_cast<TestRepMovsBytes>(test->entry())(from, to, 10);
EXPECT_EQ(to[0], '0');
for (int i = 0; i < 10; i++) {
EXPECT_EQ(from[i], to[i]);
}
EXPECT_EQ(to[10], 'y');
delete[] to;
EXPECT_DISASSEMBLY_NOT_WINDOWS(
"push rsi\n"
@ -5583,6 +5585,163 @@ ASSEMBLER_TEST_RUN(TestRepMovsBytes, test) {
"ret\n");
}
ASSEMBLER_TEST_GENERATE(TestRepMovsWords, assembler) {
__ pushq(RSI);
__ pushq(RDI);
__ pushq(CallingConventions::kArg1Reg); // from.
__ pushq(CallingConventions::kArg2Reg); // to.
__ pushq(CallingConventions::kArg3Reg); // count.
__ movq(RSI, Address(RSP, 2 * target::kWordSize)); // from.
__ movq(RDI, Address(RSP, 1 * target::kWordSize)); // to.
__ movq(RCX, Address(RSP, 0 * target::kWordSize)); // count.
__ rep_movsw();
// Remove saved arguments.
__ popq(RAX);
__ popq(RAX);
__ popq(RAX);
__ popq(RDI);
__ popq(RSI);
__ ret();
}
ASSEMBLER_TEST_RUN(TestRepMovsWords, test) {
const uint16_t from[11] = {0x0123, 0x1234, 0x2345, 0x3456, 0x4567, 0x5678,
0x6789, 0x789A, 0x89AB, 0x9ABC, 0xABCD};
uint16_t* to = new uint16_t[11];
to[10] = 0xFEFE;
typedef void (*TestRepMovsWords)(const uint16_t* from, uint16_t* to,
int count);
reinterpret_cast<TestRepMovsWords>(test->entry())(from, to, 10);
EXPECT_EQ(to[0], 0x0123u);
for (int i = 0; i < 10; i++) {
EXPECT_EQ(from[i], to[i]);
}
EXPECT_EQ(to[10], 0xFEFEu);
delete[] to;
EXPECT_DISASSEMBLY_NOT_WINDOWS(
"push rsi\n"
"push rdi\n"
"push rdi\n"
"push rsi\n"
"push rdx\n"
"movq rsi,[rsp+0x10]\n"
"movq rdi,[rsp+0x8]\n"
"movq rcx,[rsp]\n"
"rep movsw\n"
"pop rax\n"
"pop rax\n"
"pop rax\n"
"pop rdi\n"
"pop rsi\n"
"ret\n");
}
ASSEMBLER_TEST_GENERATE(TestRepMovsDwords, assembler) {
__ pushq(RSI);
__ pushq(RDI);
__ pushq(CallingConventions::kArg1Reg); // from.
__ pushq(CallingConventions::kArg2Reg); // to.
__ pushq(CallingConventions::kArg3Reg); // count.
__ movq(RSI, Address(RSP, 2 * target::kWordSize)); // from.
__ movq(RDI, Address(RSP, 1 * target::kWordSize)); // to.
__ movq(RCX, Address(RSP, 0 * target::kWordSize)); // count.
__ rep_movsl();
// Remove saved arguments.
__ popq(RAX);
__ popq(RAX);
__ popq(RAX);
__ popq(RDI);
__ popq(RSI);
__ ret();
}
ASSEMBLER_TEST_RUN(TestRepMovsDwords, test) {
const uint32_t from[11] = {0x01234567, 0x12345678, 0x23456789, 0x3456789A,
0x456789AB, 0x56789ABC, 0x6789ABCD, 0x789ABCDE,
0x89ABCDEF, 0x9ABCDEF0, 0xABCDEF01};
uint32_t* to = new uint32_t[11];
to[10] = 0xFEFEFEFE;
typedef void (*TestRepMovsDwords)(const uint32_t* from, uint32_t* to,
int count);
reinterpret_cast<TestRepMovsDwords>(test->entry())(from, to, 10);
EXPECT_EQ(to[0], 0x01234567u);
for (int i = 0; i < 10; i++) {
EXPECT_EQ(from[i], to[i]);
}
EXPECT_EQ(to[10], 0xFEFEFEFEu);
delete[] to;
EXPECT_DISASSEMBLY_NOT_WINDOWS(
"push rsi\n"
"push rdi\n"
"push rdi\n"
"push rsi\n"
"push rdx\n"
"movq rsi,[rsp+0x10]\n"
"movq rdi,[rsp+0x8]\n"
"movq rcx,[rsp]\n"
"rep movsl\n"
"pop rax\n"
"pop rax\n"
"pop rax\n"
"pop rdi\n"
"pop rsi\n"
"ret\n");
}
ASSEMBLER_TEST_GENERATE(TestRepMovsQwords, assembler) {
__ pushq(RSI);
__ pushq(RDI);
__ pushq(CallingConventions::kArg1Reg); // from.
__ pushq(CallingConventions::kArg2Reg); // to.
__ pushq(CallingConventions::kArg3Reg); // count.
__ movq(RSI, Address(RSP, 2 * target::kWordSize)); // from.
__ movq(RDI, Address(RSP, 1 * target::kWordSize)); // to.
__ movq(RCX, Address(RSP, 0 * target::kWordSize)); // count.
__ rep_movsq();
// Remove saved arguments.
__ popq(RAX);
__ popq(RAX);
__ popq(RAX);
__ popq(RDI);
__ popq(RSI);
__ ret();
}
ASSEMBLER_TEST_RUN(TestRepMovsQwords, test) {
const uint64_t from[11] = {
0x0123456789ABCDEF, 0x123456789ABCDEF0, 0x23456789ABCDEF01,
0x3456789ABCDEF012, 0x456789ABCDEF0123, 0x56789ABCDEF01234,
0x6789ABCDEF012345, 0x789ABCDEF0123456, 0x89ABCDEF01234567,
0x9ABCDEF012345678, 0xABCDEF0123456789};
uint64_t* to = new uint64_t[11];
to[10] = 0xFEFEFEFEFEFEFEFE;
typedef void (*TestRepMovsQwords)(const uint64_t* from, uint64_t* to,
int count);
reinterpret_cast<TestRepMovsQwords>(test->entry())(from, to, 10);
EXPECT_EQ(to[0], 0x0123456789ABCDEFu);
for (int i = 0; i < 10; i++) {
EXPECT_EQ(from[i], to[i]);
}
EXPECT_EQ(to[10], 0xFEFEFEFEFEFEFEFEu);
delete[] to;
EXPECT_DISASSEMBLY_NOT_WINDOWS(
"push rsi\n"
"push rdi\n"
"push rdi\n"
"push rsi\n"
"push rdx\n"
"movq rsi,[rsp+0x10]\n"
"movq rdi,[rsp+0x8]\n"
"movq rcx,[rsp]\n"
"rep movsq\n"
"pop rax\n"
"pop rax\n"
"pop rax\n"
"pop rdi\n"
"pop rsi\n"
"ret\n");
}
ASSEMBLER_TEST_GENERATE(ConditionalMovesCompare, assembler) {
__ cmpq(CallingConventions::kArg1Reg, CallingConventions::kArg2Reg);
__ movq(RDX, Immediate(1)); // Greater equal.

View file

@ -1150,7 +1150,25 @@ bool DisassemblerX64::DecodeInstructionType(uint8_t** data) {
// REP.
Print("rep ");
}
Print("%s", idesc.mnem);
if ((current & 0x01) == 0x01) {
// Operation size: word, dword or qword
switch (operand_size()) {
case WORD_SIZE:
Print("%sw", idesc.mnem);
break;
case DOUBLEWORD_SIZE:
Print("%sl", idesc.mnem);
break;
case QUADWORD_SIZE:
Print("%sq", idesc.mnem);
break;
default:
UNREACHABLE();
}
} else {
// Operation size: byte
Print("%s", idesc.mnem);
}
} else if (current == 0x99 && rex_w()) {
Print("cqo"); // Cdql is called cdq and cdqq is called cqo.
} else {

View file

@ -298,6 +298,8 @@ void ConstantPropagator::VisitStoreIndexed(StoreIndexedInstr* instr) {}
void ConstantPropagator::VisitStoreInstanceField(
StoreInstanceFieldInstr* instr) {}
void ConstantPropagator::VisitMemoryCopy(MemoryCopyInstr* instr) {}
void ConstantPropagator::VisitDeoptimize(DeoptimizeInstr* instr) {
// TODO(vegorov) remove all code after DeoptimizeInstr as dead.
}

View file

@ -377,6 +377,7 @@ struct InstrAttrs {
M(NativeParameter, kNoGC) \
M(LoadIndexedUnsafe, kNoGC) \
M(StoreIndexedUnsafe, kNoGC) \
M(MemoryCopy, kNoGC) \
M(TailCall, kNoGC) \
M(ParallelMove, kNoGC) \
M(PushArgument, kNoGC) \
@ -2691,6 +2692,86 @@ class LoadIndexedUnsafeInstr : public TemplateDefinition<1, NoThrow> {
DISALLOW_COPY_AND_ASSIGN(LoadIndexedUnsafeInstr);
};
class MemoryCopyInstr : public TemplateInstruction<5, NoThrow> {
public:
MemoryCopyInstr(Value* src,
Value* dest,
Value* src_start,
Value* dest_start,
Value* length,
classid_t src_cid,
classid_t dest_cid)
: src_cid_(src_cid),
dest_cid_(dest_cid),
element_size_(Instance::ElementSizeFor(src_cid)) {
ASSERT(IsArrayTypeSupported(src_cid));
ASSERT(IsArrayTypeSupported(dest_cid));
ASSERT(Instance::ElementSizeFor(src_cid) ==
Instance::ElementSizeFor(dest_cid));
SetInputAt(kSrcPos, src);
SetInputAt(kDestPos, dest);
SetInputAt(kSrcStartPos, src_start);
SetInputAt(kDestStartPos, dest_start);
SetInputAt(kLengthPos, length);
}
enum {
kSrcPos = 0,
kDestPos = 1,
kSrcStartPos = 2,
kDestStartPos = 3,
kLengthPos = 4
};
DECLARE_INSTRUCTION(MemoryCopy)
virtual Representation RequiredInputRepresentation(intptr_t index) const {
// All inputs are tagged (for now).
return kTagged;
}
virtual bool ComputeCanDeoptimize() const { return false; }
virtual bool HasUnknownSideEffects() const { return true; }
virtual bool AttributesEqual(Instruction* other) const { return true; }
Value* src() const { return inputs_[kSrcPos]; }
Value* dest() const { return inputs_[kDestPos]; }
Value* src_start() const { return inputs_[kSrcStartPos]; }
Value* dest_start() const { return inputs_[kDestStartPos]; }
Value* length() const { return inputs_[kLengthPos]; }
private:
// Set array_reg to point to the index indicated by start (contained in
// start_reg) of the typed data or string in array (contained in array_reg).
void EmitComputeStartPointer(FlowGraphCompiler* compiler,
classid_t array_cid,
Value* start,
Register array_reg,
Register start_reg);
static bool IsArrayTypeSupported(classid_t array_cid) {
if (IsTypedDataBaseClassId(array_cid)) {
return true;
}
switch (array_cid) {
case kOneByteStringCid:
case kTwoByteStringCid:
case kExternalOneByteStringCid:
case kExternalTwoByteStringCid:
return true;
default:
return false;
}
}
classid_t src_cid_;
classid_t dest_cid_;
intptr_t element_size_;
DISALLOW_COPY_AND_ASSIGN(MemoryCopyInstr);
};
// Unwinds the current frame and tail calls a target.
//
// The return address saved by the original caller of this frame will be in it's

View file

@ -154,6 +154,127 @@ DEFINE_BACKEND(TailCall,
__ set_constant_pool_allowed(true);
}
LocationSummary* MemoryCopyInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 5;
const intptr_t kNumTemps =
element_size_ == 16 ? 4 : element_size_ == 8 ? 2 : 1;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
locs->set_in(kSrcPos, Location::WritableRegister());
locs->set_in(kDestPos, Location::WritableRegister());
locs->set_in(kSrcStartPos, Location::RequiresRegister());
locs->set_in(kDestStartPos, Location::RequiresRegister());
locs->set_in(kLengthPos, Location::WritableRegister());
for (intptr_t i = 0; i < kNumTemps; i++) {
locs->set_temp(i, Location::RequiresRegister());
}
return locs;
}
void MemoryCopyInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
const Register src_reg = locs()->in(kSrcPos).reg();
const Register dest_reg = locs()->in(kDestPos).reg();
const Register src_start_reg = locs()->in(kSrcStartPos).reg();
const Register dest_start_reg = locs()->in(kDestStartPos).reg();
const Register length_reg = locs()->in(kLengthPos).reg();
const Register temp_reg = locs()->temp(0).reg();
RegList temp_regs = 0;
for (intptr_t i = 0; i < locs()->temp_count(); i++) {
temp_regs |= 1 << locs()->temp(i).reg();
}
EmitComputeStartPointer(compiler, src_cid_, src_start(), src_reg,
src_start_reg);
EmitComputeStartPointer(compiler, dest_cid_, dest_start(), dest_reg,
dest_start_reg);
compiler::Label loop, done;
compiler::Address src_address =
compiler::Address(src_reg, element_size_, compiler::Address::PostIndex);
compiler::Address dest_address =
compiler::Address(dest_reg, element_size_, compiler::Address::PostIndex);
// Untag length and skip copy if length is zero.
__ movs(length_reg, compiler::Operand(length_reg, ASR, 1));
__ b(&done, ZERO);
__ Bind(&loop);
switch (element_size_) {
case 1:
__ ldrb(temp_reg, src_address);
__ strb(temp_reg, dest_address);
break;
case 2:
__ ldrh(temp_reg, src_address);
__ strh(temp_reg, dest_address);
break;
case 4:
__ ldr(temp_reg, src_address);
__ str(temp_reg, dest_address);
break;
case 8:
case 16:
__ ldm(BlockAddressMode::IA_W, src_reg, temp_regs);
__ stm(BlockAddressMode::IA_W, dest_reg, temp_regs);
break;
}
__ subs(length_reg, length_reg, compiler::Operand(1));
__ b(&loop, NOT_ZERO);
__ Bind(&done);
}
void MemoryCopyInstr::EmitComputeStartPointer(FlowGraphCompiler* compiler,
classid_t array_cid,
Value* start,
Register array_reg,
Register start_reg) {
if (IsTypedDataBaseClassId(array_cid)) {
__ ldr(
array_reg,
compiler::FieldAddress(
array_reg, compiler::target::TypedDataBase::data_field_offset()));
} else {
switch (array_cid) {
case kOneByteStringCid:
__ add(
array_reg, array_reg,
compiler::Operand(compiler::target::OneByteString::data_offset() -
kHeapObjectTag));
break;
case kTwoByteStringCid:
__ add(
array_reg, array_reg,
compiler::Operand(compiler::target::OneByteString::data_offset() -
kHeapObjectTag));
break;
case kExternalOneByteStringCid:
__ ldr(array_reg,
compiler::FieldAddress(array_reg,
compiler::target::ExternalOneByteString::
external_data_offset()));
break;
case kExternalTwoByteStringCid:
__ ldr(array_reg,
compiler::FieldAddress(array_reg,
compiler::target::ExternalTwoByteString::
external_data_offset()));
break;
default:
UNREACHABLE();
break;
}
}
intptr_t shift = Utils::ShiftForPowerOfTwo(element_size_) - 1;
if (shift < 0) {
__ add(array_reg, array_reg, compiler::Operand(start_reg, ASR, -shift));
} else {
__ add(array_reg, array_reg, compiler::Operand(start_reg, LSL, shift));
}
}
LocationSummary* PushArgumentInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;

View file

@ -134,6 +134,134 @@ DEFINE_BACKEND(TailCall,
__ set_constant_pool_allowed(true);
}
LocationSummary* MemoryCopyInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 5;
const intptr_t kNumTemps = 1;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
locs->set_in(kSrcPos, Location::WritableRegister());
locs->set_in(kDestPos, Location::WritableRegister());
locs->set_in(kSrcStartPos, Location::RequiresRegister());
locs->set_in(kDestStartPos, Location::RequiresRegister());
locs->set_in(kLengthPos, Location::WritableRegister());
locs->set_temp(0, element_size_ == 16
? Location::Pair(Location::RequiresRegister(),
Location::RequiresRegister())
: Location::RequiresRegister());
return locs;
}
void MemoryCopyInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
const Register src_reg = locs()->in(kSrcPos).reg();
const Register dest_reg = locs()->in(kDestPos).reg();
const Register src_start_reg = locs()->in(kSrcStartPos).reg();
const Register dest_start_reg = locs()->in(kDestStartPos).reg();
const Register length_reg = locs()->in(kLengthPos).reg();
Register temp_reg, temp_reg2;
if (locs()->temp(0).IsPairLocation()) {
PairLocation* pair = locs()->temp(0).AsPairLocation();
temp_reg = pair->At(0).reg();
temp_reg2 = pair->At(1).reg();
} else {
temp_reg = locs()->temp(0).reg();
temp_reg2 = kNoRegister;
}
EmitComputeStartPointer(compiler, src_cid_, src_start(), src_reg,
src_start_reg);
EmitComputeStartPointer(compiler, dest_cid_, dest_start(), dest_reg,
dest_start_reg);
compiler::Label loop, done;
compiler::Address src_address =
compiler::Address(src_reg, element_size_, compiler::Address::PostIndex);
compiler::Address dest_address =
compiler::Address(dest_reg, element_size_, compiler::Address::PostIndex);
// Untag length and skip copy if length is zero.
__ adds(length_reg, ZR, compiler::Operand(length_reg, ASR, 1));
__ b(&done, ZERO);
__ Bind(&loop);
switch (element_size_) {
case 1:
__ ldr(temp_reg, src_address, kUnsignedByte);
__ str(temp_reg, dest_address, kUnsignedByte);
break;
case 2:
__ ldr(temp_reg, src_address, kUnsignedHalfword);
__ str(temp_reg, dest_address, kUnsignedHalfword);
break;
case 4:
__ ldr(temp_reg, src_address, kUnsignedWord);
__ str(temp_reg, dest_address, kUnsignedWord);
break;
case 8:
__ ldr(temp_reg, src_address, kDoubleWord);
__ str(temp_reg, dest_address, kDoubleWord);
break;
case 16:
__ ldp(temp_reg, temp_reg2, src_address, kDoubleWord);
__ stp(temp_reg, temp_reg2, dest_address, kDoubleWord);
break;
}
__ subs(length_reg, length_reg, compiler::Operand(1));
__ b(&loop, NOT_ZERO);
__ Bind(&done);
}
void MemoryCopyInstr::EmitComputeStartPointer(FlowGraphCompiler* compiler,
classid_t array_cid,
Value* start,
Register array_reg,
Register start_reg) {
if (IsTypedDataBaseClassId(array_cid)) {
__ ldr(
array_reg,
compiler::FieldAddress(
array_reg, compiler::target::TypedDataBase::data_field_offset()));
} else {
switch (array_cid) {
case kOneByteStringCid:
__ add(
array_reg, array_reg,
compiler::Operand(compiler::target::OneByteString::data_offset() -
kHeapObjectTag));
break;
case kTwoByteStringCid:
__ add(
array_reg, array_reg,
compiler::Operand(compiler::target::OneByteString::data_offset() -
kHeapObjectTag));
break;
case kExternalOneByteStringCid:
__ ldr(array_reg,
compiler::FieldAddress(array_reg,
compiler::target::ExternalOneByteString::
external_data_offset()));
break;
case kExternalTwoByteStringCid:
__ ldr(array_reg,
compiler::FieldAddress(array_reg,
compiler::target::ExternalTwoByteString::
external_data_offset()));
break;
default:
UNREACHABLE();
break;
}
}
intptr_t shift = Utils::ShiftForPowerOfTwo(element_size_) - 1;
if (shift < 0) {
__ add(array_reg, array_reg, compiler::Operand(start_reg, ASR, -shift));
} else {
__ add(array_reg, array_reg, compiler::Operand(start_reg, LSL, shift));
}
}
LocationSummary* PushArgumentInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;

View file

@ -75,6 +75,121 @@ DEFINE_BACKEND(TailCall,
__ jmp(temp);
}
LocationSummary* MemoryCopyInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 5;
const intptr_t kNumTemps = 0;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
locs->set_in(kSrcPos, Location::RequiresRegister());
locs->set_in(kDestPos, Location::RegisterLocation(EDI));
locs->set_in(kSrcStartPos, Location::WritableRegister());
locs->set_in(kDestStartPos, Location::WritableRegister());
locs->set_in(kLengthPos, Location::RegisterLocation(ECX));
return locs;
}
void MemoryCopyInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
const Register src_reg = locs()->in(kSrcPos).reg();
const Register src_start_reg = locs()->in(kSrcStartPos).reg();
const Register dest_start_reg = locs()->in(kDestStartPos).reg();
// Save ESI which is THR.
__ pushl(ESI);
__ movl(ESI, src_reg);
EmitComputeStartPointer(compiler, src_cid_, src_start(), ESI, src_start_reg);
EmitComputeStartPointer(compiler, dest_cid_, dest_start(), EDI,
dest_start_reg);
if (element_size_ <= 4) {
__ SmiUntag(ECX);
} else if (element_size_ == 16) {
__ shll(ECX, compiler::Immediate(1));
}
switch (element_size_) {
case 1:
__ rep_movsb();
break;
case 2:
__ rep_movsw();
break;
case 4:
case 8:
case 16:
__ rep_movsl();
break;
}
// Restore THR.
__ popl(ESI);
}
void MemoryCopyInstr::EmitComputeStartPointer(FlowGraphCompiler* compiler,
classid_t array_cid,
Value* start,
Register array_reg,
Register start_reg) {
intptr_t offset;
if (IsTypedDataBaseClassId(array_cid)) {
__ movl(
array_reg,
compiler::FieldAddress(
array_reg, compiler::target::TypedDataBase::data_field_offset()));
offset = 0;
} else {
switch (array_cid) {
case kOneByteStringCid:
offset =
compiler::target::OneByteString::data_offset() - kHeapObjectTag;
break;
case kTwoByteStringCid:
offset =
compiler::target::TwoByteString::data_offset() - kHeapObjectTag;
break;
case kExternalOneByteStringCid:
__ movl(array_reg,
compiler::FieldAddress(array_reg,
compiler::target::ExternalOneByteString::
external_data_offset()));
offset = 0;
break;
case kExternalTwoByteStringCid:
__ movl(array_reg,
compiler::FieldAddress(array_reg,
compiler::target::ExternalTwoByteString::
external_data_offset()));
offset = 0;
break;
default:
UNREACHABLE();
break;
}
}
ScaleFactor scale;
switch (element_size_) {
case 1:
__ SmiUntag(start_reg);
scale = TIMES_1;
break;
case 2:
scale = TIMES_1;
break;
case 4:
scale = TIMES_2;
break;
case 8:
scale = TIMES_4;
break;
case 16:
scale = TIMES_8;
break;
default:
UNREACHABLE();
break;
}
__ leal(array_reg, compiler::Address(array_reg, start_reg, scale, offset));
}
LocationSummary* PushArgumentInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;

View file

@ -127,6 +127,113 @@ DEFINE_BACKEND(TailCall, (NoLocation, Fixed<Register, ARGS_DESC_REG>)) {
__ set_constant_pool_allowed(true);
}
LocationSummary* MemoryCopyInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 5;
const intptr_t kNumTemps = 0;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
locs->set_in(kSrcPos, Location::RegisterLocation(RSI));
locs->set_in(kDestPos, Location::RegisterLocation(RDI));
locs->set_in(kSrcStartPos, Location::WritableRegister());
locs->set_in(kDestStartPos, Location::WritableRegister());
locs->set_in(kLengthPos, Location::RegisterLocation(RCX));
return locs;
}
void MemoryCopyInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
const Register src_start_reg = locs()->in(kSrcStartPos).reg();
const Register dest_start_reg = locs()->in(kDestStartPos).reg();
EmitComputeStartPointer(compiler, src_cid_, src_start(), RSI, src_start_reg);
EmitComputeStartPointer(compiler, dest_cid_, dest_start(), RDI,
dest_start_reg);
if (element_size_ <= 8) {
__ SmiUntag(RCX);
}
switch (element_size_) {
case 1:
__ rep_movsb();
break;
case 2:
__ rep_movsw();
break;
case 4:
__ rep_movsl();
break;
case 8:
case 16:
__ rep_movsq();
break;
}
}
void MemoryCopyInstr::EmitComputeStartPointer(FlowGraphCompiler* compiler,
classid_t array_cid,
Value* start,
Register array_reg,
Register start_reg) {
intptr_t offset;
if (IsTypedDataBaseClassId(array_cid)) {
__ movq(
array_reg,
compiler::FieldAddress(
array_reg, compiler::target::TypedDataBase::data_field_offset()));
offset = 0;
} else {
switch (array_cid) {
case kOneByteStringCid:
offset =
compiler::target::OneByteString::data_offset() - kHeapObjectTag;
break;
case kTwoByteStringCid:
offset =
compiler::target::TwoByteString::data_offset() - kHeapObjectTag;
break;
case kExternalOneByteStringCid:
__ movq(array_reg,
compiler::FieldAddress(array_reg,
compiler::target::ExternalOneByteString::
external_data_offset()));
offset = 0;
break;
case kExternalTwoByteStringCid:
__ movq(array_reg,
compiler::FieldAddress(array_reg,
compiler::target::ExternalTwoByteString::
external_data_offset()));
offset = 0;
break;
default:
UNREACHABLE();
break;
}
}
ScaleFactor scale;
switch (element_size_) {
case 1:
__ SmiUntag(start_reg);
scale = TIMES_1;
break;
case 2:
scale = TIMES_1;
break;
case 4:
scale = TIMES_2;
break;
case 8:
scale = TIMES_4;
break;
case 16:
scale = TIMES_8;
break;
default:
UNREACHABLE();
break;
}
__ leaq(array_reg, compiler::Address(array_reg, start_reg, scale, offset));
}
LocationSummary* PushArgumentInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;

View file

@ -245,6 +245,18 @@ Fragment BaseFlowGraphBuilder::IntConstant(int64_t value) {
Constant(Integer::ZoneHandle(Z, Integer::New(value, Heap::kOld))));
}
Fragment BaseFlowGraphBuilder::MemoryCopy(classid_t src_cid,
classid_t dest_cid) {
Value* length = Pop();
Value* dest_start = Pop();
Value* src_start = Pop();
Value* dest = Pop();
Value* src = Pop();
auto copy = new (Z) MemoryCopyInstr(src, dest, src_start, dest_start, length,
src_cid, dest_cid);
return Fragment(copy);
}
Fragment BaseFlowGraphBuilder::TailCall(const Code& code) {
Value* arg_desc = Pop();
return Fragment(new (Z) TailCallInstr(code, arg_desc));

View file

@ -285,6 +285,7 @@ class BaseFlowGraphBuilder {
intptr_t stack_depth,
intptr_t loop_depth);
Fragment CheckStackOverflowInPrologue(TokenPosition position);
Fragment MemoryCopy(classid_t src_cid, classid_t dest_cid);
Fragment TailCall(const Code& code);
Fragment Utf8Scan();

View file

@ -831,6 +831,7 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
case MethodRecognizer::kGrowableArrayCapacity:
case MethodRecognizer::kListFactory:
case MethodRecognizer::kObjectArrayAllocate:
case MethodRecognizer::kCopyRangeFromUint8ListToOneByteString:
case MethodRecognizer::kLinkedHashMap_getIndex:
case MethodRecognizer::kLinkedHashMap_setIndex:
case MethodRecognizer::kLinkedHashMap_getData:
@ -1055,6 +1056,16 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
body += LoadLocal(parsed_function_->RawParameterVariable(1));
body += CreateArray();
break;
case MethodRecognizer::kCopyRangeFromUint8ListToOneByteString:
ASSERT(function.NumParameters() == 5);
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += LoadLocal(parsed_function_->RawParameterVariable(1));
body += LoadLocal(parsed_function_->RawParameterVariable(2));
body += LoadLocal(parsed_function_->RawParameterVariable(3));
body += LoadLocal(parsed_function_->RawParameterVariable(4));
body += MemoryCopy(kTypedDataUint8ArrayCid, kOneByteStringCid);
body += NullConstant();
break;
case MethodRecognizer::kLinkedHashMap_getIndex:
ASSERT(function.NumParameters() == 1);
body += LoadLocal(parsed_function_->RawParameterVariable(0));

View file

@ -65,6 +65,8 @@ namespace dart {
V(_Int32x4ArrayView, ._, TypedData_Int32x4ArrayView_factory, 0x9bfbd6d5) \
V(_Float64x2ArrayView, ._, TypedData_Float64x2ArrayView_factory, 0x1a383408) \
V(::, _toClampedUint8, ConvertIntToClampedUint8, 0x59765a4a) \
V(::, copyRangeFromUint8ListToOneByteString, \
CopyRangeFromUint8ListToOneByteString, 0x00000000) \
V(_StringBase, _interpolate, StringBaseInterpolate, 0xc0a650e4) \
V(_IntegerImplementation, toDouble, IntegerToDouble, 0x22a26db3) \
V(_Double, _add, DoubleAdd, 0x2f5c036a) \

View file

@ -76,9 +76,9 @@ static inline Condition InvertCondition(Condition c) {
F(cdq, 0x99) \
F(fwait, 0x9B) \
F(movsb, 0xA4) \
F(movsl, 0xA5) \
F(movs, 0xA5) /* Size suffix added in code */ \
F(cmpsb, 0xA6) \
F(cmpsl, 0xA7)
F(cmps, 0xA7) /* Size suffix added in code */
// clang-format off
#define X86_ALU_CODES(F) \

View file

@ -14,6 +14,7 @@ import "dart:_internal"
allocateOneByteString,
allocateTwoByteString,
ClassID,
copyRangeFromUint8ListToOneByteString,
patch,
POWERS_OF_TEN,
unsafeCast,
@ -1751,10 +1752,9 @@ class _Utf8Decoder {
if (flags == 0) {
// Pure ASCII.
assert(size == end - start);
// TODO(dartbug.com/41703): String.fromCharCodes has a lot of overhead
// checking types and ranges, which is redundant in this case. Find a
// more direct way to do the conversion.
return String.fromCharCodes(bytes, start, end);
String result = allocateOneByteString(size);
copyRangeFromUint8ListToOneByteString(bytes, result, start, 0, size);
return result;
}
String result;
@ -1846,10 +1846,9 @@ class _Utf8Decoder {
// Pure ASCII.
assert(_state == accept);
assert(size == end - start);
// TODO(dartbug.com/41703): String.fromCharCodes has a lot of overhead
// checking types and ranges, which is redundant in this case. Find a
// more direct way to do the conversion.
return String.fromCharCodes(bytes, start, end);
String result = allocateOneByteString(size);
copyRangeFromUint8ListToOneByteString(bytes, result, start, 0, size);
return result;
}
// Do not include any final, incomplete character in size.
@ -1932,8 +1931,6 @@ class _Utf8Decoder {
String decode8(Uint8List bytes, int start, int end, int size) {
assert(start < end);
// TODO(dartbug.com/41704): Allocate an uninitialized _OneByteString and
// write characters to it using _setAt.
final String result = allocateOneByteString(size);
int i = start;
int j = 0;
@ -1986,8 +1983,6 @@ class _Utf8Decoder {
assert(start < end);
final String typeTable = _Utf8Decoder.typeTable;
final String transitionTable = _Utf8Decoder.transitionTable;
// TODO(dartbug.com/41704): Allocate an uninitialized _TwoByteString and
// write characters to it using _setAt.
final String result = allocateTwoByteString(size);
int i = start;
int j = 0;

View file

@ -17,6 +17,7 @@ import "dart:_internal"
allocateTwoByteString,
ClassID,
CodeUnits,
copyRangeFromUint8ListToOneByteString,
EfficientLengthIterable,
FixedLengthListBase,
IterableElementError,

View file

@ -12,7 +12,7 @@
import "dart:core" hide Symbol;
import "dart:isolate" show SendPort;
import "dart:typed_data" show Int32List;
import "dart:typed_data" show Int32List, Uint8List;
/// These are the additional parts of this patch library:
// part "class_id_fasta.dart";
@ -41,6 +41,18 @@ String allocateOneByteString(int length)
void writeIntoOneByteString(String string, int index, int codePoint)
native "Internal_writeIntoOneByteString";
/// This function is recognized by the VM and compiled into specialized code.
/// It is assumed that [from] is a native [Uint8List] class and [to] is a
/// [_OneByteString]. The [fromStart] and [toStart] indices together with the
/// [length] must specify ranges within the bounds of the list / string.
@pragma("vm:prefer-inline")
void copyRangeFromUint8ListToOneByteString(
Uint8List from, String to, int fromStart, int toStart, int length) {
for (int i = 0; i < length; i++) {
writeIntoOneByteString(to, toStart + i, from[fromStart + i]);
}
}
/// The returned string is a [_TwoByteString] with uninitialized content.
@pragma("vm:entry-point", "call")
String allocateTwoByteString(int length)

View file

@ -224,14 +224,14 @@ abstract class _StringBase implements String {
// It's always faster to do this in Dart than to call into the runtime.
var s = _OneByteString._allocate(len);
// Special case for _Uint8ArrayView.
if (charCodes is Uint8List) {
if (start >= 0 && len >= 0) {
for (int i = 0; i < len; i++) {
s._setAt(i, charCodes[start + i]);
}
return s;
}
// Special case for native Uint8 typed arrays.
final int cid = ClassID.getID(charCodes);
if (cid == ClassID.cidUint8ArrayView ||
cid == ClassID.cidUint8Array ||
cid == ClassID.cidExternalUint8Array) {
Uint8List bytes = unsafeCast<Uint8List>(charCodes);
copyRangeFromUint8ListToOneByteString(bytes, s, start, 0, len);
return s;
}
// Fall through to normal case.

View file

@ -12,6 +12,7 @@ import "dart:_internal"
allocateOneByteString,
allocateTwoByteString,
ClassID,
copyRangeFromUint8ListToOneByteString,
patch,
POWERS_OF_TEN,
unsafeCast,
@ -1755,10 +1756,9 @@ class _Utf8Decoder {
if (flags == 0) {
// Pure ASCII.
assert(size == end - start);
// TODO(dartbug.com/41703): String.fromCharCodes has a lot of overhead
// checking types and ranges, which is redundant in this case. Find a
// more direct way to do the conversion.
return String.fromCharCodes(bytes, start, end);
String result = allocateOneByteString(size);
copyRangeFromUint8ListToOneByteString(bytes, result, start, 0, size);
return result;
}
String result;
@ -1850,10 +1850,9 @@ class _Utf8Decoder {
// Pure ASCII.
assert(_state == accept);
assert(size == end - start);
// TODO(dartbug.com/41703): String.fromCharCodes has a lot of overhead
// checking types and ranges, which is redundant in this case. Find a
// more direct way to do the conversion.
return String.fromCharCodes(bytes, start, end);
String result = allocateOneByteString(size);
copyRangeFromUint8ListToOneByteString(bytes, result, start, 0, size);
return result;
}
// Do not include any final, incomplete character in size.
@ -1936,8 +1935,6 @@ class _Utf8Decoder {
String decode8(Uint8List bytes, int start, int end, int size) {
assert(start < end);
// TODO(dartbug.com/41704): Allocate an uninitialized _OneByteString and
// write characters to it using _setAt.
String result = allocateOneByteString(size);
int i = start;
int j = 0;
@ -1990,8 +1987,6 @@ class _Utf8Decoder {
assert(start < end);
final String typeTable = _Utf8Decoder.typeTable;
final String transitionTable = _Utf8Decoder.transitionTable;
// TODO(dartbug.com/41704): Allocate an uninitialized _TwoByteString and
// write characters to it using _setAt.
String result = allocateTwoByteString(size);
int i = start;
int j = 0;

View file

@ -15,6 +15,7 @@ import "dart:_internal"
allocateTwoByteString,
ClassID,
CodeUnits,
copyRangeFromUint8ListToOneByteString,
EfficientLengthIterable,
FixedLengthListBase,
IterableElementError,

View file

@ -10,7 +10,7 @@
import "dart:core" hide Symbol;
import "dart:isolate" show SendPort;
import "dart:typed_data" show Int32List;
import "dart:typed_data" show Int32List, Uint8List;
/// These are the additional parts of this patch library:
// part "class_id_fasta.dart";
@ -45,6 +45,18 @@ String allocateOneByteString(int length)
void writeIntoOneByteString(String string, int index, int codePoint)
native "Internal_writeIntoOneByteString";
/// This function is recognized by the VM and compiled into specialized code.
/// It is assumed that [from] is a native [Uint8List] class and [to] is a
/// [_OneByteString]. The [fromStart] and [toStart] indices together with the
/// [length] must specify ranges within the bounds of the list / string.
@pragma("vm:prefer-inline")
void copyRangeFromUint8ListToOneByteString(
Uint8List from, String to, int fromStart, int toStart, int length) {
for (int i = 0; i < length; i++) {
writeIntoOneByteString(to, toStart + i, from[fromStart + i]);
}
}
/// The returned string is a [_TwoByteString] with uninitialized content.
@pragma("vm:entry-point", "call")
String allocateTwoByteString(int length)

View file

@ -229,14 +229,14 @@ abstract class _StringBase implements String {
// It's always faster to do this in Dart than to call into the runtime.
var s = _OneByteString._allocate(len);
// Special case for _Uint8ArrayView.
if (charCodes is Uint8List) {
if (start >= 0 && len >= 0) {
for (int i = 0; i < len; i++) {
s._setAt(i, charCodes[start + i]);
}
return s;
}
// Special case for native Uint8 typed arrays.
final int cid = ClassID.getID(charCodes);
if (cid == ClassID.cidUint8ArrayView ||
cid == ClassID.cidUint8Array ||
cid == ClassID.cidExternalUint8Array) {
Uint8List bytes = unsafeCast<Uint8List>(charCodes);
copyRangeFromUint8ListToOneByteString(bytes, s, start, 0, len);
return s;
}
// Fall through to normal case.

View file

@ -35,5 +35,5 @@ MINOR 9
PATCH 0
PRERELEASE 0
PRERELEASE_PATCH 0
ABI_VERSION 35
OLDEST_SUPPORTED_ABI_VERSION 35
ABI_VERSION 36
OLDEST_SUPPORTED_ABI_VERSION 36