Skip to content

Commit

Permalink
riscv64: StringGetCharsNoCheck intrinsic.
Browse files Browse the repository at this point in the history
Implemented StringGetCharsNoCheck intrinsic.
Added new checks in the 536-checker-intrinsic-optimization test in order to increase
the quality of test coverage for the case of uncompressed strings.

Test: testrunner.py --target --64 --ndebug --no-image --jit-on-first-use

Change-Id: I08bc075440eef1750a057bad5fe6de45f792091f
  • Loading branch information
Mark authored and vmarko committed May 6, 2024
1 parent 60c54c5 commit 53da12c
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 1 deletion.
1 change: 0 additions & 1 deletion compiler/optimizing/code_generator_riscv64.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ static constexpr int32_t kFClassNaNMinValue = 0x100;
V(FP16LessEquals) \
V(FP16Min) \
V(FP16Max) \
V(StringGetCharsNoCheck) \
V(StringStringIndexOf) \
V(StringStringIndexOfAfter) \
V(StringBufferAppend) \
Expand Down
179 changes: 179 additions & 0 deletions compiler/optimizing/intrinsics_riscv64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5043,6 +5043,185 @@ void IntrinsicCodeGeneratorRISCV64::VisitMathMultiplyHigh(HInvoke* invoke) {
__ Mulh(out, x, y);
}

void IntrinsicLocationsBuilderRISCV64::VisitStringGetCharsNoCheck(HInvoke* invoke) {
LocationSummary* locations =
new (allocator_) LocationSummary(invoke, LocationSummary::kNoCall, kIntrinsified);

locations->SetInAt(0, Location::RequiresRegister());
locations->SetInAt(1, Location::RequiresRegister());
locations->SetInAt(2, Location::RequiresRegister());
locations->SetInAt(3, Location::RequiresRegister());
locations->SetInAt(4, Location::RequiresRegister());

locations->AddTemp(Location::RequiresRegister());
locations->AddTemp(Location::RequiresRegister());
locations->AddTemp(Location::RequiresRegister());
}

void IntrinsicCodeGeneratorRISCV64::VisitStringGetCharsNoCheck(HInvoke* invoke) {
Riscv64Assembler* assembler = GetAssembler();
LocationSummary* locations = invoke->GetLocations();

// In Java sizeof(Char) is 2.
constexpr size_t char_size = DataType::Size(DataType::Type::kUint16);
static_assert(char_size == 2u);

// Location of data in the destination char array buffer.
const uint32_t array_data_offset = mirror::Array::DataOffset(char_size).Uint32Value();

// Location of char array data in the source string.
const uint32_t string_value_offset = mirror::String::ValueOffset().Uint32Value();

// void getCharsNoCheck(int srcBegin, int srcEnd, char[] dst, int dstBegin);

// The source string.
XRegister source_string_object = locations->InAt(0).AsRegister<XRegister>();
// Index of the first character.
XRegister source_begin_index = locations->InAt(1).AsRegister<XRegister>();
// Index that immediately follows the last character.
XRegister source_end_index = locations->InAt(2).AsRegister<XRegister>();
// The destination array.
XRegister destination_array_object = locations->InAt(3).AsRegister<XRegister>();
// The start offset in the destination array.
XRegister destination_begin_offset = locations->InAt(4).AsRegister<XRegister>();

XRegister source_ptr = locations->GetTemp(0).AsRegister<XRegister>();
XRegister destination_ptr = locations->GetTemp(1).AsRegister<XRegister>();
XRegister number_of_chars = locations->GetTemp(2).AsRegister<XRegister>();

ScratchRegisterScope temps(assembler);
XRegister tmp = temps.AllocateXRegister();

Riscv64Label done;

// Calculate the length(number_of_chars) of the string.
__ Subw(number_of_chars, source_end_index, source_begin_index);

// If the string has zero length then exit.
__ Beqz(number_of_chars, &done);

// Prepare a register with the destination address
// to start copying to the address:
// 1. set the address from which the data in the
// destination array begins (destination_array_object + array_data_offset);
__ Addi(destination_ptr, destination_array_object, array_data_offset);
// 2. it is necessary to add the start offset relative to the beginning
// of the data in the destination array,
// yet, due to sizeof(Char) being 2, formerly scaling must be performed
// (destination_begin_offset * 2 that equals to destination_begin_offset << 1);
__ Sh1Add(destination_ptr, destination_begin_offset, destination_ptr);

// Prepare a register with the source address
// to start copying from the address:
// 1. set the address from which the data in the
// source string begins (source_string_object + string_value_offset).
// Other manipulations will be performed later,
// since they depend on whether the string is compressed or not.
__ Addi(source_ptr, source_string_object, string_value_offset);

// The string can be compressed. It is a way to store strings more compactly.
// In this instance, every character is located in one byte (instead of two).
Riscv64Label compressed_string_preloop;

// Information about whether the string is compressed or not is located
// in the area intended for storing the length of the string.
// The least significant bit of the string's length is used
// as the compression flag if STRING_COMPRESSION_ENABLED.
if (mirror::kUseStringCompression) {
// Location of count in string.
const uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
// String's length.
__ Loadwu(tmp, source_string_object, count_offset);

// Checking the string for compression.
// If so, move to the "compressed_string_preloop".
__ Andi(tmp, tmp, 0x1);
__ Beqz(tmp, &compressed_string_preloop);
}

// Continue preparing the source register:
// proceed similarly to what was done for the destination register.
__ Sh1Add(source_ptr, source_begin_index, source_ptr);

// If the string is not compressed, then perform ordinary copying.
// Copying will occur 4 characters (8 bytes) at a time, immediately after there are
// less than 4 characters left, move to the "remainder_loop" and copy the remaining
// characters one character (2 bytes) at a time.
// Note: Unaligned addresses are acceptable here and it is not required to embed
// additional code to correct them.
Riscv64Label main_loop;
Riscv64Label remainder_loop;

// If initially there are less than 4 characters,
// then we directly calculate the remainder.
__ Addi(tmp, number_of_chars, -4);
__ Bltz(tmp, &remainder_loop);

// Otherwise, save the value to the counter and continue.
__ Mv(number_of_chars, tmp);

// Main loop. Loads and stores 4 16-bit Java characters at a time.
__ Bind(&main_loop);

__ Loadd(tmp, source_ptr, 0);
__ Addi(source_ptr, source_ptr, char_size * 4);
__ Stored(tmp, destination_ptr, 0);
__ Addi(destination_ptr, destination_ptr, char_size * 4);

__ Addi(number_of_chars, number_of_chars, -4);

__ Bgez(number_of_chars, &main_loop);

// Restore the previous counter value.
__ Addi(number_of_chars, number_of_chars, 4);
__ Beqz(number_of_chars, &done);

// Remainder loop for < 4 characters case and remainder handling.
// Loads and stores one 16-bit Java character at a time.
__ Bind(&remainder_loop);

__ Loadhu(tmp, source_ptr, 0);
__ Addi(source_ptr, source_ptr, char_size);

__ Storeh(tmp, destination_ptr, 0);
__ Addi(destination_ptr, destination_ptr, char_size);

__ Addi(number_of_chars, number_of_chars, -1);
__ Bgtz(number_of_chars, &remainder_loop);

Riscv64Label compressed_string_loop;
if (mirror::kUseStringCompression) {
__ J(&done);

// Below is the copying under the string compression circumstance mentioned above.
// Every character in the source string occupies only one byte (instead of two).
constexpr size_t compressed_char_size = DataType::Size(DataType::Type::kInt8);
static_assert(compressed_char_size == 1u);

__ Bind(&compressed_string_preloop);

// Continue preparing the source register:
// proceed identically to what was done for the destination register,
// yet take into account that only one byte yields for every source character,
// hence we need to extend it to two ones when copying it to the destination address.
// Against this background scaling for source_begin_index is not needed.
__ Add(source_ptr, source_ptr, source_begin_index);

// Copy loop for compressed strings. Copying one 8-bit character to 16-bit one at a time.
__ Bind(&compressed_string_loop);

__ Loadbu(tmp, source_ptr, 0);
__ Addi(source_ptr, source_ptr, compressed_char_size);
__ Storeh(tmp, destination_ptr, 0);
__ Addi(destination_ptr, destination_ptr, char_size);

__ Addi(number_of_chars, number_of_chars, -1);
__ Bgtz(number_of_chars, &compressed_string_loop);
}

__ Bind(&done);
}

#define MARK_UNIMPLEMENTED(Name) UNIMPLEMENTED_INTRINSIC(RISCV64, Name)
UNIMPLEMENTED_INTRINSIC_LIST_RISCV64(MARK_UNIMPLEMENTED);
#undef MARK_UNIMPLEMENTED
Expand Down
44 changes: 44 additions & 0 deletions test/536-checker-intrinsic-optimization/src/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ public class Main {
public static String mediumString = generateString(300);
public static String largeString = generateString(2000);

public static String smallNonAsciiString = generateNonAsciiString(100);
public static String mediumNonAsciiString = generateNonAsciiString(300);
public static String largeNonAsciiString = generateNonAsciiString(2000);

public static String generateString(int length) {
// Generate a string in the ASCII range that will
// use string compression.
Expand All @@ -30,6 +34,14 @@ public static String generateString(int length) {
return sb.toString();
}

public static String generateNonAsciiString(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(Character.valueOf('\uFFFF'));
}
return sb.toString();
}

public static void assertIntEquals(int expected, int result) {
if (expected != result) {
throw new Error("Expected: " + expected + ", found: " + result);
Expand Down Expand Up @@ -102,6 +114,38 @@ public static void main(String[] args) {
// Substring > 8 characters.
assertStringEquals(smallString.substring(7, 28), stringGetCharsRange(smallString, 7, 28, 17));

// Single character.
assertStringEquals("\uFFFF", stringGetCharsAndBack("\uFFFF"));

// Strings < 8 characters.
assertStringEquals("\uFFFF\uFFFF\uFFFF\uFFFF\uFFFF",
stringGetCharsAndBack("\uFFFF\uFFFF\uFFFF\uFFFF\uFFFF"));

// Strings > 8 characters of various lengths.
assertStringEquals(smallNonAsciiString, stringGetCharsAndBack(smallNonAsciiString));
assertStringEquals(mediumNonAsciiString, stringGetCharsAndBack(mediumNonAsciiString));
assertStringEquals(largeNonAsciiString, stringGetCharsAndBack(largeNonAsciiString));

// Get only a substring:
// Substring < 8 characters.
assertStringEquals(smallNonAsciiString.substring(5, 10),
stringGetCharsRange(smallNonAsciiString, 5, 10, 0));
// Substring > 8 characters.
assertStringEquals(smallNonAsciiString.substring(7, 28),
stringGetCharsRange(smallNonAsciiString, 7, 28, 0));

// Get full string with offset in the char array.
assertStringEquals(smallNonAsciiString,
stringGetCharsAndBackOffset(smallNonAsciiString, 17));

// Get a substring with an offset in the char array.
// Substring < 8 characters.
assertStringEquals(smallNonAsciiString.substring(5, 10),
stringGetCharsRange(smallNonAsciiString, 5, 10, 17));
// Substring > 8 characters.
assertStringEquals(smallNonAsciiString.substring(7, 28),
stringGetCharsRange(smallNonAsciiString, 7, 28, 17));

try {
$opt$noinline$stringCharAt("abc", -1);
throw new Error("Should throw SIOOB.");
Expand Down

0 comments on commit 53da12c

Please sign in to comment.