38cfc59172
Reviewed-by: kbarrett, dholmes
305 lines
10 KiB
C++
305 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
#include "precompiled.hpp"
|
|
#include "memory/allocation.hpp"
|
|
#include "runtime/os.hpp"
|
|
#include "utilities/unsigned5.hpp"
|
|
#include "unittest.hpp"
|
|
|
|
TEST_VM(unsigned5, max_encoded_in_length) {
|
|
int maxlen = UNSIGNED5::MAX_LENGTH;
|
|
EXPECT_EQ(maxlen, 5);
|
|
for (int i = 0; i <= 190; i++) {
|
|
uint32_t interesting = i;
|
|
EXPECT_EQ(UNSIGNED5::encoded_length(interesting), 1);
|
|
EXPECT_EQ(UNSIGNED5::encoded_length(~interesting), maxlen);
|
|
}
|
|
for (int len = 1; len <= maxlen; len++) {
|
|
uint32_t interesting = UNSIGNED5::max_encoded_in_length(len);
|
|
EXPECT_EQ(UNSIGNED5::encoded_length(interesting-1), len);
|
|
EXPECT_EQ(UNSIGNED5::encoded_length(interesting), len);
|
|
if (len < 5) {
|
|
EXPECT_EQ(UNSIGNED5::encoded_length(interesting+1), len+1);
|
|
EXPECT_EQ(UNSIGNED5::encoded_length(interesting*2), len+1);
|
|
}
|
|
const int offset = -123;
|
|
const int good_limit = offset + len;
|
|
const int bad_limit = good_limit - 1;
|
|
EXPECT_TRUE(UNSIGNED5::fits_in_limit(interesting, offset, good_limit));
|
|
EXPECT_TRUE(!UNSIGNED5::fits_in_limit(interesting, offset, bad_limit));
|
|
}
|
|
}
|
|
|
|
// Call FN on a nice list of "interesting" uint32_t values to encode/decode.
|
|
// For each length in [1..5], the maximum encodable value of that
|
|
// length is "interesting", as are one more and one less than that
|
|
// value. For each nybble (aligned 4-bit field) of a uint32_t, each
|
|
// possible value (in [0..15]) stored in that nybble is "interesting".
|
|
// Also "interesting" are some other values created by perturbing
|
|
// lower bits of that nybble-bearing number, by subtracting a power
|
|
// of -7 (up to -7^7). That makes just over 1000 distinct numbers.
|
|
//
|
|
// Calls to this function are repeatable, so you can call it to pack
|
|
// an output array, and then call it again to read an input array
|
|
// verifying that the retrieved values match the stored ones.
|
|
template<typename FN>
|
|
inline int enumerate_cases(FN fn) {
|
|
// boundary values around the maximum encoded in each byte-length
|
|
for (int len = 1; len <= 5; len++) {
|
|
uint32_t interesting = UNSIGNED5::max_encoded_in_length(len);
|
|
int res = fn(interesting-1);
|
|
if (res) return res;
|
|
res = fn(interesting);
|
|
if (res) return res;
|
|
if (interesting < (uint32_t)-1) {
|
|
res = fn(interesting+1);
|
|
if (res) return res;
|
|
}
|
|
}
|
|
// for each nybble, for each value in the nybble
|
|
for (uint32_t npos = 0; npos < 32; npos += 4) {
|
|
for (uint32_t nval = 0; nval <= 15; nval++) {
|
|
uint32_t interesting = nval << npos;
|
|
int res = fn(interesting);
|
|
if (res) return res;
|
|
// mix in some crazy-looking values: powers of -7 to -7^7
|
|
for (int pon7 = 1; pon7 < 1000000; pon7 *= -7) {
|
|
uint32_t interesting2 = interesting - pon7;
|
|
res = fn(interesting2);
|
|
if (res) return res;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
TEST_VM(unsigned5, transcode_single) {
|
|
const int limit = UNSIGNED5::MAX_LENGTH;
|
|
u_char buffer[limit + 1];
|
|
auto each_case = [&](uint32_t value) -> uint32_t {
|
|
//printf("case %08X len=%d\n", value, UNSIGNED5::encoded_length(value));
|
|
int offset = 0;
|
|
UNSIGNED5::write_uint(value, buffer, offset, limit);
|
|
int length = offset;
|
|
EXPECT_TRUE(length <= UNSIGNED5::MAX_LENGTH);
|
|
EXPECT_EQ(length, UNSIGNED5::encoded_length(value)) << "for value=" << value;
|
|
buffer[length] = 0;
|
|
offset = 0;
|
|
uint32_t check = UNSIGNED5::read_uint(buffer, offset, limit);
|
|
EXPECT_EQ(offset, length) << "for value=" << value;
|
|
EXPECT_EQ(value, check);
|
|
return 0;
|
|
};
|
|
auto z = enumerate_cases(each_case);
|
|
EXPECT_TRUE(!z);
|
|
}
|
|
|
|
static int count_cases() {
|
|
int case_count = 0;
|
|
auto inc_case_count = [&](uint32_t){ ++case_count; return 0; };
|
|
enumerate_cases(inc_case_count);
|
|
return case_count;
|
|
}
|
|
|
|
TEST_VM(unsigned5, transcode_multiple) {
|
|
int case_count = count_cases();
|
|
const int limit = 200;
|
|
ASSERT_TRUE(limit < case_count*UNSIGNED5::MAX_LENGTH);
|
|
u_char buffer[limit + 1];
|
|
//printf("%d cases total\n", case_count); //1166 cases total
|
|
for (int sublimit = limit - 20; sublimit < limit; sublimit++) {
|
|
int offset = 0;
|
|
int count = 0;
|
|
// write each number into an array
|
|
auto write_case = [&](uint32_t value) -> uint32_t {
|
|
if (!UNSIGNED5::fits_in_limit(value, offset, sublimit))
|
|
return value|1;
|
|
UNSIGNED5::write_uint(value, buffer, offset, sublimit);
|
|
count++;
|
|
return 0;
|
|
};
|
|
auto done = enumerate_cases(write_case);
|
|
EXPECT_TRUE(done) << "must have hit the sublimit";
|
|
EXPECT_TRUE(count < case_count);
|
|
int length = offset;
|
|
EXPECT_TRUE(length <= sublimit && length + UNSIGNED5::MAX_LENGTH > sublimit)
|
|
<< "length=" << length << " sublimit=" << sublimit;
|
|
for (int i = length; i <= sublimit; i++) {
|
|
buffer[i] = 0;
|
|
}
|
|
if (sublimit == limit-1) {
|
|
UNSIGNED5::print_count(case_count + 1, &buffer[0], sublimit);
|
|
}
|
|
//printf("encoded %d values in %d bytes: [[%s]]\n", count, length, buffer);
|
|
// now read it all back
|
|
offset = 0;
|
|
int count2 = 0;
|
|
auto read_back_case = [&](uint32_t value) -> uint32_t {
|
|
int clen = UNSIGNED5::check_length(buffer, offset, sublimit);
|
|
if (clen == 0) return value|1;
|
|
EXPECT_EQ(clen, UNSIGNED5::encoded_length(value));
|
|
int begin = offset;
|
|
uint32_t check = UNSIGNED5::read_uint(buffer, offset, sublimit);
|
|
EXPECT_EQ(offset, begin + clen);
|
|
EXPECT_EQ(value, check);
|
|
count2++;
|
|
return 0;
|
|
};
|
|
auto done2 = enumerate_cases(read_back_case);
|
|
EXPECT_EQ(done, done2);
|
|
EXPECT_EQ(count, count2);
|
|
EXPECT_EQ(offset, length);
|
|
}
|
|
}
|
|
|
|
inline void init_ints(int len, int* ints) {
|
|
for (int i = 0; i < len; i++) {
|
|
ints[i] = (i * ((i&2) ? i : 1001)) ^ -(i & 1);
|
|
}
|
|
}
|
|
|
|
struct MyReaderHelper {
|
|
uint8_t operator()(char* a, int i) const { return a[i]; }
|
|
};
|
|
using MyReader = UNSIGNED5::Reader<char*, int, MyReaderHelper>;
|
|
|
|
TEST_VM(unsigned5, reader) {
|
|
const int LEN = 100;
|
|
int ints[LEN];
|
|
init_ints(LEN, ints);
|
|
int i;
|
|
UNSIGNED5::Sizer<> szr;
|
|
for (i = 0; i < LEN; i++) {
|
|
szr.accept_uint(ints[i]);
|
|
}
|
|
//printf("count=%d, size=%d\n", szr.count(), szr.position());
|
|
char buf[LEN * UNSIGNED5::MAX_LENGTH + 1];
|
|
int buflen;
|
|
{
|
|
int pos = 0;
|
|
for (int i = 0; i < LEN; i++) {
|
|
UNSIGNED5::write_uint(ints[i], buf, pos, 0);
|
|
}
|
|
EXPECT_TRUE(pos+1 < (int)sizeof(buf)) << pos;
|
|
buflen = pos;
|
|
buf[buflen] = 0;
|
|
}
|
|
EXPECT_EQ(szr.position(), buflen);
|
|
MyReader r1(buf);
|
|
i = 0;
|
|
while (r1.has_next()) {
|
|
int x = r1.next_uint();
|
|
int y = ints[i++];
|
|
ASSERT_EQ(x, y) << i;
|
|
}
|
|
ASSERT_EQ(i, LEN);
|
|
MyReader r2(buf, buflen / 2);
|
|
i = 0;
|
|
while (r2.has_next()) {
|
|
int x = r2.next_uint();
|
|
int y = ints[i++];
|
|
ASSERT_EQ(x, y) << i;
|
|
}
|
|
ASSERT_TRUE(i < LEN);
|
|
// copy from reader to writer
|
|
UNSIGNED5::Reader<char*,int> r3(buf);
|
|
int array_limit = 1;
|
|
char* array = new char[array_limit + 1];
|
|
auto array_grow = [&](int){
|
|
array[array_limit] = 0;
|
|
auto oal = array_limit;
|
|
array_limit += 10;
|
|
//printf("growing array from %d to %d\n", oal, array_limit);
|
|
auto na = new char[array_limit + 1];
|
|
strcpy(na, array);
|
|
array = na;
|
|
};
|
|
UNSIGNED5::Writer<char*,int> w3(array, array_limit);
|
|
while (r3.has_next()) {
|
|
w3.accept_grow(r3.next_uint(), array_grow);
|
|
}
|
|
w3.end_byte(); // we always allocated one more than the limit!
|
|
std::string buf_s(buf, buflen);
|
|
std::string arr_s(array, strlen(array));
|
|
ASSERT_EQ(buf_s, arr_s);
|
|
|
|
// try printing:
|
|
{
|
|
char stbuf[1000];
|
|
stringStream st(stbuf, sizeof(stbuf)-1);
|
|
UNSIGNED5::Reader<char*,int> printer(buf);
|
|
printer.print_on(&st, 4, "(", ")");
|
|
std::string st_s(st.base(), st.size());
|
|
char buf2[sizeof(stbuf)];
|
|
os::snprintf_checked(buf2, sizeof(buf2), "(%d %d %d %d)", ints[0], ints[1], ints[2], ints[3]);
|
|
std::string exp_s(buf2, strlen(buf2));
|
|
ASSERT_EQ(exp_s, st_s);
|
|
}
|
|
}
|
|
|
|
// Here is some object code to look at if we want to do a manual
|
|
// study. One could find the build file named test_unsigned5.o.cmdline
|
|
// and hand-edit the command line to produce assembly code in
|
|
// test_unsigned5.s.
|
|
//
|
|
// Or, given the two empty "fence functions", one could do a
|
|
// quick scan like this:
|
|
//
|
|
// $ objdump -D $(find build/*release -name test_unsigned5.o) \
|
|
// | sed -n /start_code_quality/,/end_code_quality/p \
|
|
// | egrep -B10 bswap # or grep -B20 cfi_endproc
|
|
|
|
void start_code_quality_unsigned5() { }
|
|
|
|
uint32_t code_quality_max_encoded_in_length(int i) {
|
|
return UNSIGNED5::max_encoded_in_length(i); // should compile like 5-switch
|
|
}
|
|
|
|
int code_quality_encoded_length(uint32_t x) {
|
|
return UNSIGNED5::encoded_length(x); // should compile to 4-way comparison
|
|
}
|
|
|
|
int code_quality_check_length(char* a) {
|
|
return UNSIGNED5::check_length(a, 0); // should compile with fast-path
|
|
}
|
|
|
|
int code_quality_read_int(char* a) {
|
|
int i = 0;
|
|
return UNSIGNED5::read_uint(a, i, 0); // should compile with fast-path
|
|
}
|
|
|
|
int code_quality_int_reader(char* a) {
|
|
MyReader r1(a);
|
|
if (!r1.has_next()) return -1;
|
|
return r1.next_uint();
|
|
}
|
|
|
|
int code_quality_int_sizer(int* a, int n) {
|
|
UNSIGNED5::Sizer<> s;
|
|
for (int i = 0; i < n; i++) s.accept_uint(a[i]);
|
|
return s.position();
|
|
}
|
|
|
|
void end_code_quality_unsigned5() { }
|