Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add benchmark-interp tool #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
278 changes: 278 additions & 0 deletions src/tools/benchmark-interp.cc
Original file line number Diff line number Diff line change
@@ -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 <benchmark/benchmark.h> //newline

#include <algorithm>
#include <cassert>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
#include <chrono> // newline
#include <iostream> //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<FileStream> s_log_stream;
static std::unique_ptr<FileStream> 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<uint8_t> 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;
}