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 a V8 engine #166

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions crates/artifact/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,13 @@ impl FromStr for EngineRef {
#[derive(Clone, Debug)]
pub enum WellKnownEngine {
Wasmtime,
V8,
}
impl fmt::Display for WellKnownEngine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Wasmtime => "wasmtime",
Self::V8 => "v8",
};
write!(f, "{}", s)
}
Expand All @@ -149,6 +151,7 @@ impl FromStr for WellKnownEngine {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"wasmtime" => Ok(Self::Wasmtime),
"v8" => Ok(Self::V8),
_ => Err(anyhow!("unable to parse an unknown engine: {}", s)),
}
}
Expand Down
10 changes: 9 additions & 1 deletion crates/cli/src/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ pub struct BenchmarkCommand {
/// supplied.
#[structopt(short, long, default_value = "0.01")]
significance_level: f64,

/// Sightglass checks that the benchmark has emitted log files for `stdout`
/// and `stderr` and will attempt to check these against `stdout.expected`
/// and `stderr.expected` files; enabling this flag skips these checks.
#[structopt(long)]
skip_output_check: bool,
}

impl BenchmarkCommand {
Expand Down Expand Up @@ -186,7 +192,9 @@ impl BenchmarkCommand {
&mut measurements,
)?;

self.check_output(Path::new(wasm_file), stdout, stderr)?;
if !self.skip_output_check {
self.check_output(Path::new(wasm_file), stdout, stderr)?;
}
measurements.next_iteration();
}

Expand Down
3 changes: 3 additions & 0 deletions engines/v8/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
archive
build
v8
3 changes: 3 additions & 0 deletions engines/v8/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build
v8
archive
25 changes: 25 additions & 0 deletions engines/v8/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM ubuntu:latest
ARG REPOSITORY=https://github.com/WebAssembly/wasm-c-api
ARG REVISION=branch-heads/9.9

# Install necessary dependencies
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yqq \
apt-utils \
curl \
git \
g++ \
make \
ninja-build \
pkg-config \
python3

# Setup V8 and build the `libwee8.a` library. TODO map REVISION to V8_VERSION.
WORKDIR /usr/src
COPY args.gn Makefile ./
RUN make v8-checkout
RUN make wee8

# Build the Sightglass engine.
COPY src ./src
RUN make build/libengine.so
RUN cp build/libengine.so /libengine.so
109 changes: 109 additions & 0 deletions engines/v8/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# This Makefile builds both the V8 components and the bench API wrapper for
# Sightglass.
all: v8-checkout test

######### V8 ##########

# In order to build the `libwee8` library, we must perform the following steps:
# - `make v8-checkout` will retrieve the V8 repository (borrowing much the same
# logic as https://github.com/WebAssembly/wasm-c-api)
# - `make wee8` will build the `libwee8.a` library inside the v8 tree (see See
# https://docs.google.com/document/d/1oFPHyNb_eXg6NzrE6xJDNPdJrHMZvx0LqsD6wpbd9vY/edit#)

# TODO rename `v8` directory to `upstream`; potentially move this section to a
# separate Makefile there.
V8_VERSION = branch-heads/9.9
V8_ARCH = x64
V8_MODE = release
V8_BUILD = ${V8_ARCH}.${V8_MODE}
V8_OUT = ${V8_V8}/out.gn/${V8_BUILD}
V8_DIR = v8
V8_DEPOT_TOOLS = ${V8_DIR}/depot_tools
V8_V8 = ${V8_DIR}/v8
V8_PATH = $(abspath ${V8_DEPOT_TOOLS}):${PATH}
V8_LIBWEE8 = ${V8_V8}/out/wee8/obj/libwee8.a

.PHONY: v8-checkout
v8-checkout: ${V8_DEPOT_TOOLS} ${V8_V8}
(cd ${V8_V8}; git checkout -f main)
(cd ${V8_V8}; git pull)
(cd ${V8_V8}; git checkout ${V8_VERSION})
(cd ${V8_V8}; PATH=${V8_PATH} gclient sync)
mkdir -p ${V8_OUT}
echo >${V8_OUT}/version ${V8_VERSION}
${V8_DEPOT_TOOLS}:
mkdir -p ${V8_DIR}
(cd ${V8_DIR}; git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git --depth 1)
${V8_V8}:
mkdir -p ${V8_DIR}
(cd ${V8_DIR}; PATH=${V8_PATH} fetch --no-history v8)
(cd ${V8_V8}; git checkout ${V8_VERSION})

.PHONY: wee8
wee8: ${V8_LIBWEE8}
${V8_LIBWEE8}: args.gn
mkdir -p ${V8_V8}/out/wee8
cp args.gn ${V8_V8}/out/wee8/
(cd ${V8_V8}; PATH=${V8_PATH} gn gen out/wee8)
(cd ${V8_V8}; PATH=${V8_PATH} autoninja -C out/wee8 wee8)

.PHONY: v8-clean
v8-clean:
rm -rf v8



######### BENCH-API ##########

# The shared library that Sightglass expects must conform to a specific
# interface (see `src/bench-api.h`). We interact with `libwee8.a` in
# `src/bench-state.cc`. To build and test this functionality, use `make test`.

# This Makefile setup roughly follows https://stackoverflow.com/a/2481326.
CXXFLAGS=-g -O0 -fPIC -I ${V8_V8}/third_party/wasm-api
WASM_HH=${V8_V8}/third_party/wasm-api/wasm.hh
SRC_DIR=src
TEST_DIR=test
BUILD_DIR=build
SRCS=src/bench-api.cc src/bench-state.cc src/link.cc src/wasi.cc
OBJS=$(patsubst $(SRC_DIR)/%.cc,$(BUILD_DIR)/%.o,$(SRCS))
LDLIBS=$(V8_LIBWEE8) -ldl -pthread
# See https://stackoverflow.com/questions/36692315.
LDFLAGS=-rdynamic

# Create the build directory, if necessary.
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)

# Built-in rules to compile the various source files.
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cc
$(CXX) $(CXXFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(TEST_DIR)/%.c
$(CC) $(CFLAGS) -I $(SRC_DIR) -c $< -o $@

# Define the dependencies for each object.
$(BUILD_DIR)/bench-api.o: $(SRC_DIR)/bench-api.cc $(SRC_DIR)/bench-api.h $(WASM_HH)
$(BUILD_DIR)/bench-state.o: $(SRC_DIR)/bench-state.cc $(SRC_DIR)/bench-state.hh $(SRC_DIR)/bench-api.h $(WASM_HH)
$(BUILD_DIR)/link.o: $(SRC_DIR)/link.cc $(SRC_DIR)/link.hh $(SRC_DIR)/bench-state.hh $(SRC_DIR)/wasi.hh $(WASM_HH)
$(BUILD_DIR)/wasi.o: $(SRC_DIR)/wasi.cc $(SRC_DIR)/wasi.hh $(WASM_HH)
$(BUILD_DIR)/main.o: $(TEST_DIR)/main.c $(SRC_DIR)/bench-api.h

# Create a test target for building and running the test file with `make test`.
.PHONY: test
test: $(BUILD_DIR)/test
$< ../../benchmarks-next/noop/benchmark.wasm
$(BUILD_DIR)/test: $(BUILD_DIR) $(OBJS) $(BUILD_DIR)/main.o
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(BUILD_DIR)/main.o $(LDLIBS)

# Create the shared library target; this is what can be used by sightglass to
# measure benchmarks.
$(BUILD_DIR)/libengine.so: $(BUILD_DIR) $(OBJS)
$(CXX) -shared $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
test-lib: $(BUILD_DIR)/libengine.so
RUST_LOG=trace cargo run -- benchmark ../../benchmarks-next/noop/benchmark.wasm \
--engine $(BUILD_DIR)/libengine.so --processes 1 --iterations-per-process 1 \
--skip-output-check

.PHONY: test
clean:
rm -rf build
9 changes: 9 additions & 0 deletions engines/v8/args.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
is_component_build = false
use_custom_libcxx = false
v8_enable_fast_mksnapshot = true
v8_enable_i18n_support = false
v8_use_external_startup_data = false
is_debug = true
symbol_level = 2
v8_optimized_debug = false
Comment on lines +6 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we are building V8 with debug symbols and/or without optimizations?

v8_expose_symbols = true
103 changes: 103 additions & 0 deletions engines/v8/src/bench-api.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include "bench-api.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <map>
#include <optional>
#include <utility>
#include <vector>
#include "bench-state.hh"
#include "link.hh"
#include "wasm.hh"

extern "C" {

int wasm_bench_create(wasm_bench_config_t config, void** out_ptr) {
BenchState* st = new BenchState(Config::make(config));
st->engine = wasm::Engine::make(); // TODO cannot instantiate an EngineImpl
// multiple times.
Comment on lines +18 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you do something like

static wasm::Engine* engine;
if (engine == nullptr) {
    engine = wasm::Engine::make();
}
st->engine = engine;

here?

assert(st->engine != nullptr);
st->store = wasm::Store::make(st->engine.get());
assert(st->store != nullptr);

assert(out_ptr != NULL);
*out_ptr = (void*)st;
return 0;
}

void wasm_bench_free(void* state_) {
BenchState* state = (BenchState*)state_;
delete state;
}

int wasm_bench_compile(void* state_,
char* wasm_bytes,
size_t wasm_bytes_length) {
BenchState* st = (BenchState*)state_;
assert(st != nullptr);
assert(st->store != nullptr);
auto wasm = wasm::vec<byte_t>::make(wasm_bytes_length, wasm_bytes);

st->config.compilation.start();
st->module = wasm::Module::make(st->store.get(), wasm);
st->config.compilation.end();

if (!st->module) {
std::cerr << "> Error compiling module!" << std::endl;
return 1;
} else {
return 0;
}
}

int wasm_bench_instantiate(void* state_) {
BenchState* st = (BenchState*)state_;
assert(st != nullptr);
assert(st->module != nullptr);
assert(st->store != nullptr);

// Retrieve linked functions and extract their pointers here to avoid dropping
// them too early.
auto imports = link(st->store.get(), st->module.get(), &st->config.execution);
const wasm::Extern* imports_as_extern[imports.size()];
for (size_t i = 0; i < imports.size(); ++i) {
imports_as_extern[i] = imports[i].get();
}

st->config.instantiation.start();
st->instance = wasm::Instance::make(st->store.get(), st->module.get(),
imports_as_extern);
st->config.instantiation.end();

if (!st->instance) {
std::cerr << "> Error instantiating module!" << std::endl;
return 1;
} else {
return 0;
}
}

int wasm_bench_execute(void* state_) {
BenchState* st = (BenchState*)state_;
assert(st != nullptr);
assert(st->module != nullptr);
assert(st->instance != nullptr);

// Find the _start function.
auto start_fn = find_start_fn(st->module.get(), st->instance.get());
if (!start_fn) {
std::cerr << "> Unable to find the '_start' function!" << std::endl;
return 1;
}

// Run the start function.
if (!start_fn->func()->call()) {
std::cerr << "> Error calling start function!" << std::endl;
return 1;
}

return 0;
}

} // extern "C"
50 changes: 50 additions & 0 deletions engines/v8/src/bench-api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Describe the C API that this engine must conform to for use within
// Sightglass.
#ifndef __BENCH_API_H
#define __BENCH_API_H

#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef char* wasm_bench_timer_t;
typedef void (*wasm_bench_callback_t)(wasm_bench_timer_t);

/**
* @brief This struct must match what is passed by sightglass (TODO cite).
*/
typedef struct wasm_bench_config_t {
char* working_dir_ptr;
size_t working_dir_len;
char* stdout_path_ptr;
size_t stdout_path_len;
char* stderr_path_ptr;
size_t stderr_path_len;
char* stdin_path_ptr;
size_t stdin_path_len;
wasm_bench_timer_t compilation_timer;
wasm_bench_callback_t compilation_start;
wasm_bench_callback_t compilation_end;
wasm_bench_timer_t instantiation_timer;
wasm_bench_callback_t instantiation_start;
wasm_bench_callback_t instantiation_end;
wasm_bench_timer_t execution_timer;
wasm_bench_callback_t execution_start;
wasm_bench_callback_t execution_end;
} wasm_bench_config_t;

/// API functions (TODO cite).

int wasm_bench_create(wasm_bench_config_t config, void** state_out_ptr);
void wasm_bench_free(void* state);
int wasm_bench_compile(void* state, char* wasm_bytes, size_t wasm_bytes_length);
int wasm_bench_instantiate(void* state);
int wasm_bench_execute(void* state);

#ifdef __cplusplus
} // extern "C"
#endif

#endif // #ifdef __BENCH_API_H
33 changes: 33 additions & 0 deletions engines/v8/src/bench-state.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include "bench-state.hh"
#include <iostream>

Config Config::make(wasm_bench_config_t config) {
auto working_dir =
std::string(config.working_dir_ptr, config.working_dir_len);
auto stdout_path =
std::string(config.stdout_path_ptr, config.stdout_path_len);
auto stderr_path =
std::string(config.stderr_path_ptr, config.stderr_path_len);
auto stdin_path = std::string(config.stdin_path_ptr, config.stdin_path_len);
auto compilation = Timer(config.compilation_timer, config.compilation_start,
config.compilation_end);
auto instantiation =
Timer(config.instantiation_timer, config.instantiation_start,
config.instantiation_end);
auto execution = Timer(config.execution_timer, config.execution_start,
config.execution_end);
return Config(working_dir, stdout_path, stderr_path, stdin_path, compilation,
instantiation, execution);
}

void Timer::start() const {
if (this->start_timer != NULL) {
(this->start_timer)(this->timer);
}
}

void Timer::end() const {
if (this->end_timer != NULL) {
(this->end_timer)(this->timer);
}
}
Loading