[runtime] Zero padding of sequential strings

SeqStrings have up to kTaggedSize-1 bytes of padding, due to
allocation sizes being rounded up to kObjectAlignment. This CL ensures
that all (non-external) sequential strings on the heap have (and keep)
this padding set to all zeroes.

The approach is to unconditionally zero the last tagged word of the
uninitialized allocation of string objects.

Change-Id: I32ee4a53e25fa470f79562a71b8c648c7205523f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4143019
Commit-Queue: Olivier Flückiger <olivf@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85294}
This commit is contained in:
Olivier Flückiger 2023-01-09 16:32:41 +00:00 committed by V8 LUCI CQ
parent e02e128455
commit 44df2a5bd5
14 changed files with 72 additions and 11 deletions

View File

@ -575,6 +575,7 @@ void ProcessorImpl::ToStringImpl(char* out, int* out_length, Digits X,
}
int excess = formatter.Finish();
*out_length -= excess;
memset(out + *out_length, 0, excess);
}
Status Processor::ToString(char* out, int* out_length, Digits X, int radix,

View File

@ -488,6 +488,8 @@ const kFixedDoubleArrayMaxLength:
constexpr int31 generates 'FixedDoubleArray::kMaxLength';
const kObjectAlignmentMask: constexpr intptr
generates 'kObjectAlignmentMask';
const kObjectAlignment: constexpr intptr
generates 'kObjectAlignment';
const kMinAddedElementsCapacity:
constexpr int31 generates 'JSObject::kMinAddedElementsCapacity';
const kMaxFastArrayLength:

View File

@ -267,17 +267,23 @@ extern macro Allocate(
const kAllocateBaseFlags: constexpr AllocationFlag =
AllocationFlag::kAllowLargeObjectAllocation;
macro AllocateFromNew(
sizeInBytes: intptr, map: Map, pretenured: bool): UninitializedHeapObject {
sizeInBytes: intptr, map: Map, pretenured: bool,
clearPadding: bool): UninitializedHeapObject {
dcheck(ValidAllocationSize(sizeInBytes, map));
let res: UninitializedHeapObject;
if (pretenured) {
return Allocate(
res = Allocate(
sizeInBytes,
%RawConstexprCast<constexpr AllocationFlag>(
%RawConstexprCast<constexpr int32>(kAllocateBaseFlags) |
%RawConstexprCast<constexpr int32>(AllocationFlag::kPretenured)));
} else {
return Allocate(sizeInBytes, kAllocateBaseFlags);
res = Allocate(sizeInBytes, kAllocateBaseFlags);
}
if (clearPadding) {
*unsafe::NewReference<Zero>(res, sizeInBytes - kObjectAlignment) = kZero;
}
return res;
}
macro InitializeFieldsFromIterator<T: type, Iterator: type>(

View File

@ -3725,6 +3725,10 @@ TNode<String> CodeStubAssembler::AllocateSeqOneByteString(
return EmptyStringConstant();
}
TNode<HeapObject> result = Allocate(SeqOneByteString::SizeFor(length), flags);
StoreNoWriteBarrier(MachineRepresentation::kTaggedSigned, result,
IntPtrConstant(SeqOneByteString::SizeFor(length) -
kObjectAlignment - kHeapObjectTag),
SmiConstant(0));
DCHECK(RootsTable::IsImmortalImmovable(RootIndex::kOneByteStringMap));
StoreMapNoWriteBarrier(result, RootIndex::kOneByteStringMap);
StoreObjectFieldNoWriteBarrier(result, SeqOneByteString::kLengthOffset,
@ -3747,6 +3751,10 @@ TNode<String> CodeStubAssembler::AllocateSeqTwoByteString(
return EmptyStringConstant();
}
TNode<HeapObject> result = Allocate(SeqTwoByteString::SizeFor(length), flags);
StoreNoWriteBarrier(MachineRepresentation::kTaggedSigned, result,
IntPtrConstant(SeqTwoByteString::SizeFor(length) -
kObjectAlignment - kHeapObjectTag),
SmiConstant(0));
DCHECK(RootsTable::IsImmortalImmovable(RootIndex::kStringMap));
StoreMapNoWriteBarrier(result, RootIndex::kStringMap);
StoreObjectFieldNoWriteBarrier(result, SeqTwoByteString::kLengthOffset,

View File

@ -4305,6 +4305,11 @@ Node* EffectControlLinearizer::LowerStringFromSingleCharCode(Node* node) {
Node* vfalse1 =
__ Allocate(AllocationType::kYoung,
__ IntPtrConstant(SeqTwoByteString::SizeFor(1)));
__ Store(StoreRepresentation(MachineRepresentation::kTaggedSigned,
kNoWriteBarrier),
vfalse1,
SeqTwoByteString::SizeFor(1) - kObjectAlignment - kHeapObjectTag,
__ SmiConstant(0));
__ StoreField(AccessBuilder::ForMap(), vfalse1,
__ HeapConstant(factory()->string_map()));
__ StoreField(AccessBuilder::ForNameRawHashField(), vfalse1,
@ -4401,6 +4406,11 @@ Node* EffectControlLinearizer::LowerStringFromSingleCodePoint(Node* node) {
Node* vfalse1 =
__ Allocate(AllocationType::kYoung,
__ IntPtrConstant(SeqTwoByteString::SizeFor(1)));
__ Store(StoreRepresentation(MachineRepresentation::kTaggedSigned,
kNoWriteBarrier),
vfalse1,
SeqTwoByteString::SizeFor(1) - kObjectAlignment - kHeapObjectTag,
__ SmiConstant(0));
__ StoreField(AccessBuilder::ForMap(), vfalse1,
__ HeapConstant(factory()->string_map()));
__ StoreField(AccessBuilder::ForNameRawHashField(), vfalse1,
@ -4441,6 +4451,11 @@ Node* EffectControlLinearizer::LowerStringFromSingleCodePoint(Node* node) {
Node* vfalse0 =
__ Allocate(AllocationType::kYoung,
__ IntPtrConstant(SeqTwoByteString::SizeFor(2)));
__ Store(StoreRepresentation(MachineRepresentation::kTaggedSigned,
kNoWriteBarrier),
vfalse0,
SeqTwoByteString::SizeFor(2) - kObjectAlignment - kHeapObjectTag,
__ SmiConstant(0));
__ StoreField(AccessBuilder::ForMap(), vfalse0,
__ HeapConstant(factory()->string_map()));
__ StoreField(AccessBuilder::ForNameRawHashField(), vfalse0,

View File

@ -342,9 +342,12 @@ void MaglevAssembler::MaybeEmitDeoptBuiltinsCall(size_t eager_deopt_count,
void MaglevAssembler::AllocateTwoByteString(RegisterSnapshot register_snapshot,
Register result, int length) {
Allocate(register_snapshot, result, SeqTwoByteString::SizeFor(length));
int size = SeqTwoByteString::SizeFor(length);
Allocate(register_snapshot, result, size);
UseScratchRegisterScope scope(this);
Register scratch = scope.AcquireX();
Move(scratch, 0);
StoreTaggedField(scratch, FieldMemOperand(result, size - kObjectAlignment));
LoadRoot(scratch, RootIndex::kStringMap);
StoreTaggedField(scratch, FieldMemOperand(result, HeapObject::kMapOffset));
Move(scratch, Name::kEmptyHashField);

View File

@ -87,8 +87,10 @@ void MaglevAssembler::AllocateHeapNumber(RegisterSnapshot register_snapshot,
void MaglevAssembler::AllocateTwoByteString(RegisterSnapshot register_snapshot,
Register result, int length) {
Allocate(register_snapshot, result, SeqTwoByteString::SizeFor(length));
int size = SeqTwoByteString::SizeFor(length);
Allocate(register_snapshot, result, size);
LoadRoot(kScratchRegister, RootIndex::kStringMap);
StoreTaggedField(FieldOperand(result, size - kObjectAlignment), Immediate(0));
StoreTaggedField(FieldOperand(result, HeapObject::kMapOffset),
kScratchRegister);
StoreTaggedField(FieldOperand(result, Name::kRawHashFieldOffset),

View File

@ -969,6 +969,7 @@ MaybeHandle<String> BigInt::ToString(Isolate* isolate, Handle<BigInt> bigint,
// out whether all characters were used.
chars_written = chars_allocated - static_cast<int>(out - start);
std::memmove(start, out, chars_written);
memset(start + chars_written, 0, chars_allocated - chars_written);
}
} else {
// Generic path, handles anything.

View File

@ -1850,6 +1850,18 @@ SeqString::DataAndPaddingSizes SeqTwoByteString::GetDataAndPaddingSizes()
return DataAndPaddingSizes{data_size, padding_size};
}
#ifdef VERIFY_HEAP
V8_EXPORT_PRIVATE void SeqString::SeqStringVerify(Isolate* isolate) {
TorqueGeneratedSeqString<SeqString, String>::SeqStringVerify(isolate);
DataAndPaddingSizes sz = GetDataAndPaddingSizes();
auto padding = reinterpret_cast<char*>(address() + sz.data_size);
CHECK(sz.padding_size <= kTaggedSize);
for (int i = 0; i < sz.padding_size; ++i) {
CHECK_EQ(padding[i], 0);
}
}
#endif // VERIFY_HEAP
void SeqString::ClearPadding() {
DataAndPaddingSizes sz = GetDataAndPaddingSizes();
DCHECK_EQ(address() + sz.data_size + sz.padding_size, address() + Size());

View File

@ -721,6 +721,10 @@ class SeqString : public TorqueGeneratedSeqString<SeqString, String> {
// Zero out only the padding bytes of this string.
void ClearPadding();
#ifdef VERIFY_HEAP
V8_EXPORT_PRIVATE void SeqStringVerify(Isolate* isolate);
#endif
TQ_OBJECT_CONSTRUCTORS(SeqString)
};

View File

@ -134,7 +134,7 @@ type DirectString extends String;
macro AllocateNonEmptySeqOneByteString<Iterator: type>(
length: uint32, content: Iterator): SeqOneByteString {
dcheck(length != 0 && length <= kStringMaxLength);
return new SeqOneByteString{
return new (ClearPadding) SeqOneByteString{
map: kOneByteStringMap,
raw_hash_field: kNameEmptyHashField,
length: Signed(length),
@ -145,7 +145,7 @@ macro AllocateNonEmptySeqOneByteString<Iterator: type>(
macro AllocateNonEmptySeqTwoByteString<Iterator: type>(
length: uint32, content: Iterator): SeqTwoByteString {
dcheck(length > 0 && length <= kStringMaxLength);
return new SeqTwoByteString{
return new (ClearPadding) SeqTwoByteString{
map: kStringMap,
raw_hash_field: kNameEmptyHashField,
length: Signed(length),

View File

@ -595,11 +595,13 @@ struct AssumeTypeImpossibleExpression : Expression {
struct NewExpression : Expression {
DEFINE_AST_NODE_LEAF_BOILERPLATE(NewExpression)
NewExpression(SourcePosition pos, TypeExpression* type,
std::vector<NameAndExpression> initializers, bool pretenured)
std::vector<NameAndExpression> initializers, bool pretenured,
bool clear_padding)
: Expression(kKind, pos),
type(type),
initializers(std::move(initializers)),
pretenured(pretenured) {}
pretenured(pretenured),
clear_padding(clear_padding) {}
void VisitAllSubExpressions(VisitCallback callback) override {
for (auto& initializer : initializers) {
@ -611,6 +613,7 @@ struct NewExpression : Expression {
TypeExpression* type;
std::vector<NameAndExpression> initializers;
bool pretenured;
bool clear_padding;
};
enum class ImplicitKind { kNoImplicit, kJSImplicit, kImplicit };

View File

@ -1691,6 +1691,8 @@ VisitResult ImplementationVisitor::Visit(NewExpression* expr) {
allocate_arguments.parameters.push_back(object_map);
allocate_arguments.parameters.push_back(
GenerateBoolConstant(expr->pretenured));
allocate_arguments.parameters.push_back(
GenerateBoolConstant(expr->clear_padding));
VisitResult allocate_result = GenerateCall(
QualifiedName({TORQUE_INTERNAL_NAMESPACE_STRING}, "AllocateFromNew"),
allocate_arguments, {class_type}, false);

View File

@ -420,12 +420,13 @@ base::Optional<ParseResult> MakeMethodCall(ParseResultIterator* child_results) {
base::Optional<ParseResult> MakeNewExpression(
ParseResultIterator* child_results) {
bool pretenured = child_results->NextAs<bool>();
bool clear_padding = child_results->NextAs<bool>();
auto type = child_results->NextAs<TypeExpression*>();
auto initializers = child_results->NextAs<std::vector<NameAndExpression>>();
Expression* result =
MakeNode<NewExpression>(type, std::move(initializers), pretenured);
Expression* result = MakeNode<NewExpression>(type, std::move(initializers),
pretenured, clear_padding);
return ParseResult{result};
}
@ -2565,6 +2566,7 @@ struct TorqueGrammar : Grammar {
Symbol newExpression = {
Rule({Token("new"),
CheckIf(Sequence({Token("("), Token("Pretenured"), Token(")")})),
CheckIf(Sequence({Token("("), Token("ClearPadding"), Token(")")})),
&simpleType, &initializerList},
MakeNewExpression)};