From dc9e350fded4653186df74d1454c95a73c37cb7f Mon Sep 17 00:00:00 2001 From: cdetrio Date: Mon, 29 Jul 2019 16:24:18 -0400 Subject: [PATCH] add benchmark-interp tool --- CMakeLists.txt | 14 ++ src/tools/benchmark-interp.cc | 278 ++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 src/tools/benchmark-interp.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index c81e92d07..3fb9979ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,6 +276,9 @@ if (NOT EMSCRIPTEN) add_executable(${EXE_NAME} ${EXE_SOURCES}) add_dependencies(everything ${EXE_NAME}) target_link_libraries(${EXE_NAME} ${EXE_LIBS}) + # TODO: include google/benchmark in third_party + # the libbenchmark.a location used here assumes a google/benchmark global install + target_link_libraries(${EXE_NAME} /usr/local/lib/libbenchmark.a) set_property(TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11) set_property(TARGET ${EXE_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) @@ -342,6 +345,14 @@ if (NOT EMSCRIPTEN) INSTALL ) + # benchmark-interp + wabt_executable( + NAME benchmark-interp + SOURCES src/tools/benchmark-interp.cc + WITH_LIBM + INSTALL + ) + # spectest-interp wabt_executable( NAME spectest-interp @@ -382,6 +393,9 @@ if (NOT EMSCRIPTEN) include_directories( third_party/gtest/googletest third_party/gtest/googletest/include + # /usr/local/include is for a google/benchmark global install + # TODO: include google/benchmark in third_party + /usr/local/include ) # gtest diff --git a/src/tools/benchmark-interp.cc b/src/tools/benchmark-interp.cc new file mode 100644 index 000000000..68e01ebf4 --- /dev/null +++ b/src/tools/benchmark-interp.cc @@ -0,0 +1,278 @@ +/* + * Copyright 2016 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include //newline + +#include +#include +#include +#include +#include +#include +#include +#include +#include // newline +#include //ne4wline + +#include "src/binary-reader.h" +#include "src/cast.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/interp/binary-reader-interp.h" +#include "src/interp/interp.h" +#include "src/literal.h" +#include "src/option-parser.h" +#include "src/resolve-names.h" +#include "src/stream.h" +#include "src/validator.h" +#include "src/wast-lexer.h" +#include "src/wast-parser.h" + +using namespace wabt; +using namespace wabt::interp; + +using chrono_clock = std::chrono::high_resolution_clock; //newline + +static int s_verbose; +static const char* s_infile; +static Thread::Options s_thread_options; +static Stream* s_trace_stream; +static bool s_host_print; +static bool s_dummy_import_func; +static Features s_features; + +static std::unique_ptr s_log_stream; +static std::unique_ptr s_stdout_stream; + +enum class RunVerbosity { + Quiet = 0, + Verbose = 1, +}; + +static const char s_description[] = + R"( read a file in the wasm binary format, and run in it a stack-based + interpreter. + +examples: + # parse binary file test.wasm, and type-check it + $ wasm-interp test.wasm + + # parse test.wasm and run all its exported functions + $ wasm-interp test.wasm --run-all-exports + + # parse test.wasm, run the exported functions and trace the output + $ wasm-interp test.wasm --run-all-exports --trace + + # parse test.wasm and run all its exported functions, setting the + # value stack size to 100 elements + $ wasm-interp test.wasm -V 100 --run-all-exports +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wasm-interp", s_description); + + parser.AddOption('v', "verbose", "Use multiple times for more info", []() { + s_verbose++; + s_log_stream = FileStream::CreateStdout(); + }); + parser.AddHelpOption(); + s_features.AddOptions(&parser); + parser.AddOption('V', "value-stack-size", "SIZE", + "Size in elements of the value stack", + [](const std::string& argument) { + // TODO(binji): validate. + s_thread_options.value_stack_size = atoi(argument.c_str()); + }); + parser.AddOption('C', "call-stack-size", "SIZE", + "Size in elements of the call stack", + [](const std::string& argument) { + // TODO(binji): validate. + s_thread_options.call_stack_size = atoi(argument.c_str()); + }); + parser.AddOption('t', "trace", "Trace execution", + []() { s_trace_stream = s_stdout_stream.get(); }); + parser.AddOption("host-print", + "Include an importable function named \"host.print\" for " + "printing to stdout", + []() { s_host_print = true; }); + + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { s_infile = argument; }); + parser.Parse(argc, argv); +} + + +static wabt::Result ReadModule(const char* module_filename, + Environment* env, + Errors* errors, + DefinedModule** out_module) { + wabt::Result result; + std::vector file_data; + + *out_module = nullptr; + + result = ReadFile(module_filename, &file_data); + if (Succeeded(result)) { + const bool kReadDebugNames = true; + const bool kStopOnFirstError = true; + const bool kFailOnCustomSectionError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, + kStopOnFirstError, kFailOnCustomSectionError); + result = ReadBinaryInterp(env, file_data.data(), file_data.size(), options, + errors, out_module); + + if (Succeeded(result)) { + if (s_verbose) { + env->DisassembleModule(s_stdout_stream.get(), *out_module); + } + } + } + return result; +} + +static interp::Result PrintCallback(const HostFunc* func, + const interp::FuncSignature* sig, + const TypedValues& args, + TypedValues& results) { + printf("called host "); + WriteCall(s_stdout_stream.get(), func->module_name, func->field_name, args, + results, interp::Result::Ok); + return interp::Result::Ok; +} + +static void InitEnvironment(Environment* env) { + if (s_host_print) { + auto* host_module = env->AppendHostModule("host"); + host_module->on_unknown_func_export = + [](Environment* env, HostModule* host_module, string_view name, + Index sig_index) -> Index { + if (name != "print") { + return kInvalidIndex; + } + + return host_module->AppendFuncExport(name, sig_index, PrintCallback) + .second; + }; + } + + if (s_dummy_import_func) { + env->on_unknown_module = [](Environment* env, string_view name) { + auto* host_module = env->AppendHostModule(name); + host_module->on_unknown_func_export = + [](Environment* env, HostModule* host_module, string_view name, + Index sig_index) -> Index { + return host_module->AppendFuncExport(name, sig_index, PrintCallback) + .second; + }; + return true; + }; + } + + // TODO: call AppendScoutFuncs +} + + +static wabt::Result ReadModuleAndRunMainCheckSuccess(const char* module_filename) { + wabt::Result result; + Environment env; + InitEnvironment(&env); + + Errors errors; + // TODO: for a benchmark loop that doesn't instantiate and only executes, use globals for `env` and `module` + DefinedModule* module = nullptr; + result = ReadModule(module_filename, &env, &errors, &module); + FormatErrorsToFile(errors, Location::Type::Binary); + if (Succeeded(result)) { + printf("parse succeeded...\n"); + Executor executor(&env, s_trace_stream, s_thread_options); + TypedValues args; + interp::Export* main_ = module->GetExport("main"); + ExecResult exec_result = executor.RunExport(main_, args); + if (exec_result.result == interp::Result::Ok) { + printf("execution succeeded.\n"); + return wabt::Result::Ok; + } + } + return wabt::Result::Error; +} + + +static wabt::Result ReadModuleAndRunMain(const char* module_filename) { + wabt::Result result; + Environment env; + InitEnvironment(&env); + + Errors errors; + // TODO: for a benchmark loop that doesn't instantiate and only executes, use globals for `env` and `module` + DefinedModule* module = nullptr; + result = ReadModule(module_filename, &env, &errors, &module); + FormatErrorsToFile(errors, Location::Type::Binary); + // check that result succeeded was already done in first run + Executor executor(&env, s_trace_stream, s_thread_options); + // to benchmark an ewasm module, just run main + TypedValues args; + interp::Export* main_ = module->GetExport("main"); + ExecResult exec_result = executor.RunExport(main_, args); + // this result should always succeed, maybe remove check? + if (exec_result.result == interp::Result::Ok) { + return wabt::Result::Ok; + } + return wabt::Result::Error; +} + +using namespace benchmark; + +namespace +{ + + // the benchmark function cannot take any inputs except `state`, which is provided by the benchmark lib + void wabt_interp(State& state) noexcept + { + wabt::Result result; + + for (auto _ : state) { + // TODO: could be more efficient about instantiating the module + // to benchmark just the execution time, only need to reset memory and globals + result = ReadModuleAndRunMain(s_infile); + } + + // TODO: report instantiation and execution time separately using `Counter`?? + } + +} // namespace benchmark + + +int main(int argc, char** argv) +{ + // Initialize is a benchmark.h function + Initialize(&argc, argv); + + InitStdio(); + s_stdout_stream = FileStream::CreateStdout(); + ParseOptions(argc, argv); + + // do one run and check that execution succeeds before starting benchmark loop + wabt::Result result = ReadModuleAndRunMainCheckSuccess(s_infile); + if (Succeeded(result)) { + printf("run benchmark loop...\n"); + RegisterBenchmark("wabt_interp", wabt_interp)->Unit(kMicrosecond); + RunSpecifiedBenchmarks(); + return 0; + } else { + printf("ERROR: execution failed.\n"); + } + return result != wabt::Result::Ok; +}