/* * 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 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; 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 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 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 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() { }