diff --git a/codecov.yml b/codecov.yml index 03abe2daeb2..9f2569b0669 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,3 +2,4 @@ ignore: - "test/" - "src/driver" - "build/" + - "src/netron_output.cpp" diff --git a/requirements.txt b/requirements.txt index 4daf866ed52..d3edaa80070 100755 --- a/requirements.txt +++ b/requirements.txt @@ -28,4 +28,4 @@ pybind/pybind11@3e9dfa2866941655c56877882565e7577de6fc7b --build msgpack/msgpack-c@cpp-3.3.0 -DMSGPACK_BUILD_TESTS=Off sqlite3@3.43.2 -DCMAKE_POSITION_INDEPENDENT_CODE=On ROCm/composable_kernel@b7775add2d28251674d81e220cd4a857b90b997a -DCK_BUILD_JIT_LIB=On -DCMAKE_POSITION_INDEPENDENT_CODE=On -ROCm/rocMLIR@13065c4b3a216e1b13dfb8f746b8a0d421f124e8 -DBUILD_FAT_LIBROCKCOMPILER=On \ No newline at end of file +ROCm/rocMLIR@13065c4b3a216e1b13dfb8f746b8a0d421f124e8 -DBUILD_FAT_LIBROCKCOMPILER=On diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eda6ea626e4..550bb30bd42 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,7 @@ add_library(migraphx argument.cpp autocast_fp8.cpp auto_contiguous.cpp + base64.cpp common.cpp common_dims.cpp compile_src.cpp @@ -73,6 +74,7 @@ add_library(migraphx memory_coloring.cpp module.cpp msgpack.cpp + netron_output.cpp normalize_attributes.cpp normalize_ops.cpp op_enums.cpp diff --git a/src/base64.cpp b/src/base64.cpp new file mode 100644 index 00000000000..0d08b1f6220 --- /dev/null +++ b/src/base64.cpp @@ -0,0 +1,81 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { + +namespace { +using byte = unsigned char; + +std::array constexpr b64_chars{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; + +/// base64 encoder snippet altered from https://stackoverflow.com/a/37109258 +std::string encode(const std::vector& buf) +{ + std::size_t len = buf.size(); + std::vector res_vec((len + 2) / 3 * 4, '='); + std::size_t j = 0; + std::size_t remaining = len % 3; + const size_t last = len - remaining; + + for(size_t i = 0; i < last; i += 3) + { + std::size_t n = static_cast(buf.at(i)) << 16u | + static_cast(buf.at(i + 1)) << 8u | + static_cast(buf.at(i + 2)); + res_vec.at(j++) = b64_chars.at(n >> 18u); + res_vec.at(j++) = b64_chars.at(n >> 12u & 0x3Fu); + res_vec.at(j++) = b64_chars.at(n >> 6u & 0x3Fu); + res_vec.at(j++) = b64_chars.at(n & 0x3Fu); + } + // Set padding + if(remaining != 0) + { + std::size_t n = --remaining == 0 ? static_cast(buf.at(last)) + : static_cast(buf.at(last)) << 8u | + static_cast(buf.at(last + 1)); + res_vec.at(j++) = b64_chars.at(remaining == 0 ? n >> 2u : n >> 10u & 0x3Fu); + res_vec.at(j++) = b64_chars.at(remaining == 0 ? n << 4u & 0x3Fu : n >> 4u & 0x03Fu); + res_vec.at(j++) = remaining == 0 ? '=' : b64_chars.at(n << 2u & 0x3Fu); + } + return {res_vec.begin(), res_vec.end()}; +} + +} // namespace + +std::string base64_encode(const std::string& str) +{ + return encode(std::vector(str.begin(), str.end())); +} + +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/driver/main.cpp b/src/driver/main.cpp index aba29975e4b..01d59804e2d 100644 --- a/src/driver/main.cpp +++ b/src/driver/main.cpp @@ -56,6 +56,8 @@ #include #include +#include + #include namespace migraphx { @@ -166,6 +168,10 @@ struct loader {"--binary"}, ap.help("Print out program in binary format."), ap.set_value("binary")); + ap(output_type, + {"--netron"}, + ap.help("Print out program as Netron readable json."), + ap.set_value("netron")); ap(output, {"--output", "-o"}, ap.help("Output to file.")); } @@ -418,6 +424,8 @@ struct loader *os << to_json_string(p.to_value()) << std::endl; else if(type == "binary") write(*os, save_buffer(p)); + else if(type == "netron") + *os << make_netron_output(p) << std::endl; } }; diff --git a/src/include/migraphx/base64.hpp b/src/include/migraphx/base64.hpp new file mode 100644 index 00000000000..36035430826 --- /dev/null +++ b/src/include/migraphx/base64.hpp @@ -0,0 +1,39 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MIGRAPHX_GUARD_RTGLIB_BASE64_HPP +#define MIGRAPHX_GUARD_RTGLIB_BASE64_HPP + +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { + +/// encode string to base64 +std::string base64_encode(const std::string& str); + +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx + +#endif diff --git a/src/include/migraphx/netron_output.hpp b/src/include/migraphx/netron_output.hpp new file mode 100644 index 00000000000..fb355a2d9f5 --- /dev/null +++ b/src/include/migraphx/netron_output.hpp @@ -0,0 +1,39 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MIGRAPHX_GUARD_RTGLIB_NETRON_OUTPUT_HPP +#define MIGRAPHX_GUARD_RTGLIB_NETRON_OUTPUT_HPP + +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { + +MIGRAPHX_EXPORT std::string make_netron_output(const program& prog); + +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx + +#endif diff --git a/src/netron_output.cpp b/src/netron_output.cpp new file mode 100644 index 00000000000..64403dd7090 --- /dev/null +++ b/src/netron_output.cpp @@ -0,0 +1,266 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace { + +// from https://onnx.ai/onnx/intro/concepts.html +int get_onnx_type(shape::type_t s_type) +{ + switch(s_type) + { + case shape::float_type: return 1; + case shape::uint8_type: return 2; + case shape::int8_type: return 3; + case shape::uint16_type: return 4; + case shape::int16_type: return 5; + case shape::int32_type: return 6; + case shape::int64_type: return 7; + case shape::bool_type: return 9; + case shape::half_type: return 10; + case shape::double_type: return 11; + case shape::uint32_type: return 12; + case shape::uint64_type: return 13; + case shape::bf16_type: return 16; + case shape::fp8e4m3fn_type: return 17; + case shape::fp8e4m3fnuz_type: return 18; + case shape::fp8e5m2_type: return 19; + case shape::fp8e5m2fnuz_type: return 20; + case shape::tuple_type: return 0; + } + MIGRAPHX_THROW("MIGraphX type " + std::to_string(s_type) + " not supported"); +} + +auto make_attribute(const migraphx::value& val) +{ + value attribute; + attribute["name"] = val.get_key(); + auto val_string = val.to(); + val_string = val_string.substr(val_string.find(":") + 1); + attribute["s"] = base64_encode(val_string); + attribute["type"] = "STRING"; + return attribute; +} + +/// Returns a value with the JSON structure needed for a node +auto make_onnx_json_node(instruction_ref ins, + std::unordered_map ins_uids) +{ + value node; + // TODO add support for module inputs + value input_arr; + for(instruction_ref input_ins : ins->inputs()) + { + auto name = input_ins->name(); + if(name == "@literal" or name == "@param") + { + input_arr.push_back(ins_uids.at(input_ins)); + } + // TODO make a better process for handling nodes to ignore + else if(name.find("hip::hip_allocate_memory") != std::string::npos) + { + continue; + } + else + { + input_arr.push_back(ins_uids.at(input_ins) + "->" + ins_uids.at(ins)); + } + } + value output_arr; + for(instruction_ref output_ins : ins->outputs()) + { + if(output_ins->name() == "@return") + { + output_arr.push_back(ins_uids.at(output_ins)); + } + else + { + output_arr.push_back(ins_uids.at(ins) + "->" + ins_uids.at(output_ins)); + } + } + node["input"] = input_arr; + node["output"] = output_arr; + node["name"] = ins_uids.at(ins); + node["opType"] = ins->name(); + value op_attribute_arr; + auto op_value = ins->get_operator().to_value(); + std::for_each(op_value.begin(), op_value.end(), [&](auto v) { + const std::string& attr_key = v.get_key(); + if(v.is_binary()) + { + return; + } + else if(attr_key == "symbol_name" or attr_key == "name") + { + node["opType"] = migraphx::from_value(v); + } + else + { + op_attribute_arr.push_back(make_attribute(v)); + } + }); + node["attribute"] = op_attribute_arr; + return node; +} + +// ONNX graph constant data called "initializer" +auto make_onnx_json_literal(instruction_ref ins, + std::unordered_map ins_uids) +{ + value lit; + lit["dims"] = ins->get_shape().lens(); + lit["dataType"] = get_onnx_type(ins->get_shape().type()); + lit["name"] = ins_uids.at(ins); + // ignoring literal data, setting to "NULL" in base64 + lit["rawData"] = "TlVMTA=="; + return lit; +} + +// TODO handle dynamic shapes +// TODO handle subshapes +auto make_onnx_json_shape(const shape& s) +{ + value ret; + value dim; + auto shape_lens = s.lens(); + std::transform(shape_lens.begin(), + shape_lens.end(), + std::back_inserter(dim), + [](std::size_t len) { return len; }); + ret["dim"] = dim; + return ret; +} + +// ONNX graph edges called "valuetype" +auto make_onnx_json_edge(instruction_ref ins, + instruction_ref out_ins, + std::unordered_map ins_uids) +{ + value ret; + shape ins_shape = ins->get_shape(); + ret["name"] = ins_uids.at(ins) + "->" + ins_uids.at(out_ins); + value type = {{"tensorType", + {{"elemType", get_onnx_type(ins_shape.type())}, + {"shape", make_onnx_json_shape(ins_shape)}}}}; + ret["type"] = type; + return ret; +} + +auto make_onnx_json_in_out(instruction_ref ins, + std::unordered_map ins_uids) +{ + value ret; + shape ins_shape = ins->get_shape(); + ret["name"] = ins_uids.at(ins); + value type = {{"tensorType", + {{"elemType", get_onnx_type(ins_shape.type())}, + {"shape", make_onnx_json_shape(ins_shape)}}}}; + ret["type"] = type; + return ret; +} + +std::unordered_map make_ins_uids(const module& mod) +{ + std::unordered_map ret; + int count = 0; + for(auto ins : iterator_for(mod)) + { + std::string var_name; + var_name = mod.name() + ":"; + var_name.append(ins->name() + ":"); + var_name.append("@" + std::to_string(count)); + count++; + ret.emplace(ins, var_name); + } + return ret; +} + +value make_graph(const module* mod) +{ + value graph = { + {"node", {}}, {"initializer", {}}, {"input", {}}, {"output", {}}, {"valueInfo", {}}}; + auto ins_uids = make_ins_uids(*mod); + for(auto ins = mod->begin(); ins != mod->end(); ++ins) + { + const auto& name = ins->name(); + if(name == "@literal") + { + graph["initializer"].push_back(make_onnx_json_literal(ins, ins_uids)); + } + else if(name == "@param") + { + graph["input"].push_back(make_onnx_json_in_out(ins, ins_uids)); + } + else if(name == "@return") + { + graph["output"].push_back(make_onnx_json_in_out(ins, ins_uids)); + } + else if(name.find("hip::hip_allocate_memory") != std::string::npos) + { + continue; + } + else + { + graph["node"].push_back(make_onnx_json_node(ins, ins_uids)); + const auto& outputs = ins->outputs(); + for(auto out_ins : outputs) + { + if(out_ins->name() != "@return") + { + graph["valueInfo"].push_back(make_onnx_json_edge(ins, out_ins, ins_uids)); + } + } + } + } + return graph; +} + +} // namespace + +std::string make_netron_output(const program& prog) +{ + value output; + auto prog_value = prog.to_value(); + output["irVersion"] = prog_value.at("version").to(); + output["producerName"] = "AMDMIGraphX"; + output["producerVersion"] = prog_value.at("migraphx_version").to(); + for(auto& mod : prog.get_modules()) + { + auto graph = make_graph(mod); + output["graph"] = graph; + } + return to_json_string(output); +} + +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/test/base64_test.cpp b/test/base64_test.cpp new file mode 100644 index 00000000000..65b8c3a8800 --- /dev/null +++ b/test/base64_test.cpp @@ -0,0 +1,140 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include "test.hpp" + +TEST_CASE(base64_encoding) +{ + EXPECT(migraphx::base64_encode("abc") == "YWJj"); + + EXPECT(migraphx::base64_encode("abcd") == "YWJjZA=="); + + EXPECT(migraphx::base64_encode("convolution") == "Y29udm9sdXRpb24="); + + EXPECT(migraphx::base64_encode("https://www.amd.com/en/products/software/rocm.html") == + "aHR0cHM6Ly93d3cuYW1kLmNvbS9lbi9wcm9kdWN0cy9zb2Z0d2FyZS9yb2NtLmh0bWw="); + + EXPECT(migraphx::base64_encode("{1, 3, 7, 9}") == "ezEsIDMsIDcsIDl9"); +} + +TEST_CASE(base64_RFC_test_vectors) +{ + EXPECT(migraphx::base64_encode("") == ""); + + EXPECT(migraphx::base64_encode("f") == "Zg=="); + + EXPECT(migraphx::base64_encode("fo") == "Zm8="); + + EXPECT(migraphx::base64_encode("foo") == "Zm9v"); + + EXPECT(migraphx::base64_encode("foob") == "Zm9vYg=="); + + EXPECT(migraphx::base64_encode("fooba") == "Zm9vYmE="); + + EXPECT(migraphx::base64_encode("foobar") == "Zm9vYmFy"); +} + +// Following tests altered from +// https://github.com/tobiaslocker/base64/blob/master/test/base64_tests.cpp +TEST_CASE(base64_encodes_three_bytes_zeros) +{ + std::array const input{0x00, 0x00, 0x00}; + std::string expected{"AAAA"}; + std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +TEST_CASE(base64_encodes_three_bytes_random) +{ + std::array const input{0xFE, 0xE9, 0x72}; + std::string const expected{"/uly"}; + std::string const actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +TEST_CASE(base64_encodes_two_bytes) +{ + std::array const input{0x00, 0x00}; + std::string expected{"AAA="}; + std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +TEST_CASE(base64_encodes_one_byte) +{ + std::array const input{0x00}; + std::string expected{"AA=="}; + std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +TEST_CASE(base64_encodes_four_bytes) +{ + std::array const input{0x74, 0x68, 0x65, 0x20}; + std::string expected{"dGhlIA=="}; + std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +TEST_CASE(base64_encodes_five_bytes) +{ + std::array const input{0x20, 0x62, 0x72, 0x6f, 0x77}; + std::string expected{"IGJyb3c="}; + std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +TEST_CASE(base64_encodes_six_bytes) +{ + std::array const input{0x20, 0x6a, 0x75, 0x6d, 0x70, 0x73}; + std::string expected{"IGp1bXBz"}; + std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +TEST_CASE(base64_encodes_BrownFox) +{ + std::array const input{ + 0x74, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x20, 0x62, 0x72, 0x6f, 0x77, 0x6e, + 0x20, 0x66, 0x6f, 0x78, 0x20, 0x6a, 0x75, 0x6d, 0x70, 0x73, 0x20, 0x6f, 0x76, 0x65, 0x72, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x67}; + + std::string expected{"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="}; + std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +TEST_CASE(base64_encodes_EncodesBrownFastFoxNullInMiddle) +{ + std::array const input{ + 0x74, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x21, 0x20, 0x62, 0x72, 0x6f, 0x77, + 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x20, 0x6a, 0x75, 0x6d, 0x70, 0x73, 0x20, 0x6f, 0x76, 0x65, + 0x72, 0x20, 0x74, 0x68, 0x65, 0x00, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x67}; + + std::string expected{"dGhlIHF1aWNrISBicm93biBmb3gganVtcHMgb3ZlciB0aGUAIGxhenkgZG9n"}; + std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; + EXPECT(expected == actual); +} + +int main(int argc, const char* argv[]) { test::run(argc, argv); }