Skip to content

wasi: support monotonic clock on clock_time_get. #156

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

Merged
merged 4 commits into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 18 additions & 3 deletions src/exports.cc
Original file line number Diff line number Diff line change
Expand Up @@ -833,16 +833,31 @@ Word wasi_unstable_args_sizes_get(void *raw_context, Word argc_ptr, Word argv_bu
Word wasi_unstable_clock_time_get(void *raw_context, Word clock_id, uint64_t precision,
Word result_time_uint64_ptr) {

if (clock_id != 0 /* realtime */) {
#if !defined(_MSC_VER)
clockid_t id = CLOCK_REALTIME;
switch (clock_id) {
case 0 /* realtime */:
break;
case 1 /* monotonic */:
id = CLOCK_MONOTONIC;
break;
default:
// process_cputime_id and thread_cputime_id are not supported yet.
return 58; // __WASI_ENOTSUP
}

struct timespec tpe;
clock_gettime(id, &tpe);
uint64_t result = tpe.tv_sec;
result *= 1000000000;
result += tpe.tv_nsec;
auto context = WASM_CONTEXT(raw_context);
uint64_t result = context->getCurrentTimeNanoseconds();
if (!context->wasm()->setDatatype(result_time_uint64_ptr, result)) {
return 21; // __WASI_EFAULT
}
return 0; // __WASI_ESUCCESS
#else
return 58; // __WASI_ENOTSUP;
#endif
}

// __wasi_errno_t __wasi_random_get(uint8_t *buf, size_t buf_len);
Expand Down
1 change: 1 addition & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ cc_test(
srcs = ["exports_test.cc"],
copts = COPTS,
data = [
"//test/test_data:clock.wasm",
"//test/test_data:env.wasm",
],
linkopts = LINKOPTS,
Expand Down
53 changes: 33 additions & 20 deletions test/exports_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,47 +48,60 @@ class TestContext : public ContextBase {
};

TEST_P(TestVM, Environment) {
// Initialize VM.
std::unordered_map<std::string, std::string> envs = {{"KEY1", "VALUE1"}, {"KEY2", "VALUE2"}};
initialize("env.wasm");
initialize(readTestWasmFile("env.wasm"), "vm_id", "", "", envs);

auto wasm_base = WasmBase(std::move(vm_), "vm_id", "", "", envs, {});
ASSERT_TRUE(wasm_base.wasm_vm()->load(source_, false));

TestContext context(&wasm_base);
// Initialize the context.
TestContext context(wasmBase());
current_context_ = &context;

wasm_base.registerCallbacks();

ASSERT_TRUE(wasm_base.wasm_vm()->link(""));

// Call the exported function.
WasmCallVoid<0> run;
wasm_base.wasm_vm()->getFunction("run", &run);

wasmBase()->wasm_vm()->getFunction("run", &run);
run(current_context_);

// Check logs.
auto msg = context.log_msg();
EXPECT_NE(std::string::npos, msg.find("KEY1: VALUE1\n")) << msg;
EXPECT_NE(std::string::npos, msg.find("KEY2: VALUE2\n")) << msg;
}

TEST_P(TestVM, WithoutEnvironment) {
initialize("env.wasm");
auto wasm_base = WasmBase(std::move(vm_), "vm_id", "", "", {}, {});
ASSERT_TRUE(wasm_base.wasm_vm()->load(source_, false));
// Initialize VM.
initialize(readTestWasmFile("env.wasm"));

TestContext context(&wasm_base);
// Initialize the context.
TestContext context(wasmBase());
current_context_ = &context;

wasm_base.registerCallbacks();
// Call the exported function.
WasmCallVoid<0> run;
wasmBase()->wasm_vm()->getFunction("run", &run);
run(current_context_);

// Check logs.
EXPECT_EQ(context.log_msg(), "");
}

ASSERT_TRUE(wasm_base.wasm_vm()->link(""));
TEST_P(TestVM, Clock) {
// Initialize VM.
initialize(readTestWasmFile("clock.wasm"));

WasmCallVoid<0> run;
wasm_base.wasm_vm()->getFunction("run", &run);
// Initialize the context.
TestContext context(wasmBase());
current_context_ = &context;

// Call the exported function.
WasmCallVoid<0> run;
wasmBase()->wasm_vm()->getFunction("run", &run);
ASSERT_TRUE(run);
run(current_context_);

EXPECT_EQ(context.log_msg(), "");
// Check logs.
auto msg = context.log_msg();
EXPECT_NE(std::string::npos, msg.find("monotonic: ")) << msg;
EXPECT_NE(std::string::npos, msg.find("realtime: ")) << msg;
}

} // namespace
Expand Down
89 changes: 41 additions & 48 deletions test/runtime_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,44 +34,39 @@ auto test_values = testing::ValuesIn(getRuntimes());
INSTANTIATE_TEST_SUITE_P(Runtimes, TestVM, test_values);

TEST_P(TestVM, Basic) {
EXPECT_EQ(vm_->cloneable(), proxy_wasm::Cloneable::CompiledBytecode);
EXPECT_EQ(vm_->runtime(), runtime_);
EXPECT_EQ(wasmVm()->cloneable(), proxy_wasm::Cloneable::CompiledBytecode);
EXPECT_EQ(wasmVm()->runtime(), runtime());
}

TEST_P(TestVM, ABIVersion) {
initialize("abi_export.wasm");
ASSERT_TRUE(vm_->load(source_, false));
ASSERT_EQ(vm_->getAbiVersion(), AbiVersion::ProxyWasm_0_2_0);
initialize(readTestWasmFile("abi_export.wasm"));
ASSERT_EQ(wasmVm()->getAbiVersion(), AbiVersion::ProxyWasm_0_2_0);
}

TEST_P(TestVM, Memory) {
initialize("abi_export.wasm");
ASSERT_TRUE(vm_->load(source_, false));
ASSERT_TRUE(vm_->link(""));
initialize(readTestWasmFile("abi_export.wasm"));

Word word;
ASSERT_TRUE(vm_->setWord(0x2000, Word(100)));
ASSERT_TRUE(vm_->getWord(0x2000, &word));
ASSERT_TRUE(wasmVm()->setWord(0x2000, Word(100)));
ASSERT_TRUE(wasmVm()->getWord(0x2000, &word));
ASSERT_EQ(100, word.u64_);

int32_t data[2] = {-1, 200};
ASSERT_TRUE(vm_->setMemory(0x200, sizeof(int32_t) * 2, static_cast<void *>(data)));
ASSERT_TRUE(vm_->getWord(0x200, &word));
ASSERT_TRUE(wasmVm()->setMemory(0x200, sizeof(int32_t) * 2, static_cast<void *>(data)));
ASSERT_TRUE(wasmVm()->getWord(0x200, &word));
ASSERT_EQ(-1, static_cast<int32_t>(word.u64_));
ASSERT_TRUE(vm_->getWord(0x204, &word));
ASSERT_TRUE(wasmVm()->getWord(0x204, &word));
ASSERT_EQ(200, static_cast<int32_t>(word.u64_));
}

TEST_P(TestVM, Clone) {
initialize("abi_export.wasm");
ASSERT_TRUE(vm_->load(source_, false));
ASSERT_TRUE(vm_->link(""));
initialize(readTestWasmFile("abi_export.wasm"));
const auto address = 0x2000;
Word word;
{
auto clone = vm_->clone();
auto clone = wasmVm()->clone();
ASSERT_TRUE(clone != nullptr);
ASSERT_NE(vm_, clone);
ASSERT_NE(wasmVm(), clone.get());
ASSERT_TRUE(clone->link(""));

ASSERT_TRUE(clone->setWord(address, Word(100)));
Expand All @@ -80,7 +75,7 @@ TEST_P(TestVM, Clone) {
}

// check memory arrays are not overrapped
ASSERT_TRUE(vm_->getWord(address, &word));
ASSERT_TRUE(wasmVm()->getWord(address, &word));
ASSERT_NE(100, word.u64_);
}

Expand All @@ -101,81 +96,79 @@ void callback(void *raw_context) {
Word callback2(void *raw_context, Word val) { return val + 100; }

TEST_P(TestVM, StraceLogLevel) {
initialize("callback.wasm");
ASSERT_TRUE(vm_->load(source_, false));
vm_->registerCallback("env", "callback", &nopCallback,
&ConvertFunctionWordToUint32<decltype(nopCallback),
nopCallback>::convertFunctionWordToUint32);
vm_->registerCallback(
ASSERT_TRUE(wasmVm()->load(readTestWasmFile("callback.wasm"), false));
wasmVm()->registerCallback(
"env", "callback", &nopCallback,
&ConvertFunctionWordToUint32<decltype(nopCallback),
nopCallback>::convertFunctionWordToUint32);
wasmVm()->registerCallback(
"env", "callback2", &callback2,
&ConvertFunctionWordToUint32<decltype(callback2), callback2>::convertFunctionWordToUint32);
ASSERT_TRUE(vm_->link(""));
ASSERT_TRUE(wasmVm()->link(""));

WasmCallVoid<0> run;
vm_->getFunction("run", &run);
wasmVm()->getFunction("run", &run);

run(nullptr);
// no trace message found since DummyIntegration's log_level_ defaults to LogLevel::info
EXPECT_EQ(integration_->trace_message_, "");
EXPECT_EQ(integration()->trace_message_, "");

integration_->log_level_ = LogLevel::trace;
integration()->log_level_ = LogLevel::trace;
run(nullptr);
EXPECT_NE(integration_->trace_message_, "");
EXPECT_NE(integration()->trace_message_, "");
}

TEST_P(TestVM, Callback) {
initialize("callback.wasm");
ASSERT_TRUE(vm_->load(source_, false));
ASSERT_TRUE(wasmVm()->load(readTestWasmFile("callback.wasm"), false));

TestContext context;
current_context_ = &context;

vm_->registerCallback(
wasmVm()->registerCallback(
"env", "callback", &callback,
&ConvertFunctionWordToUint32<decltype(callback), callback>::convertFunctionWordToUint32);

vm_->registerCallback(
wasmVm()->registerCallback(
"env", "callback2", &callback2,
&ConvertFunctionWordToUint32<decltype(callback2), callback2>::convertFunctionWordToUint32);

ASSERT_TRUE(vm_->link(""));
ASSERT_TRUE(wasmVm()->link(""));

WasmCallVoid<0> run;
vm_->getFunction("run", &run);
wasmVm()->getFunction("run", &run);
EXPECT_TRUE(run != nullptr);
for (auto i = 0; i < 100; i++) {
run(current_context_);
}
ASSERT_EQ(context.counter, 100);

WasmCallWord<1> run2;
vm_->getFunction("run2", &run2);
wasmVm()->getFunction("run2", &run2);
Word res = run2(current_context_, Word{0});
ASSERT_EQ(res.u32(), 100100); // 10000 (global) + 100(in callback)
}

TEST_P(TestVM, Trap) {
initialize("trap.wasm");
ASSERT_TRUE(vm_->load(source_, false));
ASSERT_TRUE(vm_->link(""));
ASSERT_TRUE(wasmVm()->load(readTestWasmFile("trap.wasm"), false));
ASSERT_TRUE(wasmVm()->link(""));
WasmCallVoid<0> trigger;
vm_->getFunction("trigger", &trigger);
wasmVm()->getFunction("trigger", &trigger);
EXPECT_TRUE(trigger != nullptr);
trigger(current_context_);
std::string exp_message = "Function: trigger failed";
ASSERT_TRUE(integration_->error_message_.find(exp_message) != std::string::npos);
ASSERT_TRUE(integration()->error_message_.find(exp_message) != std::string::npos);

WasmCallWord<1> trigger2;
vm_->getFunction("trigger2", &trigger2);
wasmVm()->getFunction("trigger2", &trigger2);
EXPECT_TRUE(trigger2 != nullptr);
trigger2(current_context_, 0);
exp_message = "Function: trigger2 failed:";
ASSERT_TRUE(integration_->error_message_.find(exp_message) != std::string::npos);
ASSERT_TRUE(integration()->error_message_.find(exp_message) != std::string::npos);
}

TEST_P(TestVM, WithPrecompiledSection) {
// Verify that stripping precompile_* custom section works.
initialize("abi_export.wasm");
auto source = readTestWasmFile("abi_export.wasm");
// Append precompiled_test section
std::vector<char> custom_section = {// custom section id
0x00,
Expand All @@ -189,9 +182,9 @@ TEST_P(TestVM, WithPrecompiledSection) {
// content
0x01, 0x01};

source_.append(custom_section.data(), custom_section.size());
ASSERT_TRUE(vm_->load(source_, false));
ASSERT_EQ(vm_->getAbiVersion(), AbiVersion::ProxyWasm_0_2_0);
source.append(custom_section.data(), custom_section.size());
ASSERT_TRUE(wasmVm()->load(source, false));
ASSERT_EQ(wasmVm()->getAbiVersion(), AbiVersion::ProxyWasm_0_2_0);
}

} // namespace
Expand Down
6 changes: 6 additions & 0 deletions test/test_data/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ wasm_rust_binary(
srcs = ["env.rs"],
wasi = True,
)

wasm_rust_binary(
name = "clock.wasm",
srcs = ["clock.rs"],
wasi = True,
)
21 changes: 21 additions & 0 deletions test/test_data/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2021 Google LLC
//
// 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.

use std::time::{Instant, SystemTime};

#[no_mangle]
pub extern "C" fn run() {
println!("monotonic: {:?}", Instant::now());
println!("realtime: {:?}", SystemTime::now());
}
32 changes: 22 additions & 10 deletions test/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,32 +62,44 @@ struct DummyIntegration : public WasmVmIntegration {

class TestVM : public testing::TestWithParam<std::string> {
public:
std::unique_ptr<proxy_wasm::WasmVm> vm_;

TestVM() : integration_(new DummyIntegration{}) {
runtime_ = GetParam();
TestVM() : integration_(new DummyIntegration{}), runtime_(GetParam()) {
if (runtime_ == "") {
EXPECT_TRUE(false) << "runtime must not be empty";
#if defined(WASM_V8)
} else if (runtime_ == "v8") {
vm_ = proxy_wasm::createV8Vm();
wasm_vm_ = proxy_wasm::createV8Vm();
#endif
#if defined(WASM_WAVM)
} else if (runtime_ == "wavm") {
vm_ = proxy_wasm::createWavmVm();
wasm_vm_ = proxy_wasm::createWavmVm();
#endif
#if defined(WASM_WASMTIME)
} else if (runtime_ == "wasmtime") {
vm_ = proxy_wasm::createWasmtimeVm();
wasm_vm_ = proxy_wasm::createWasmtimeVm();
#endif
}
vm_->integration().reset(integration_);
wasm_vm_->integration().reset(integration_);
}

void initialize(std::string filename) { source_ = readTestWasmFile(filename); }
void initialize(const std::string source, std::string_view vm_id = "",
std::string_view vm_configuration = "", std::string_view vm_key = "",
std::unordered_map<std::string, std::string> envs = {},
AllowedCapabilitiesMap allowed_capabilities = {}) {
wasm_base_ = std::make_unique<WasmBase>(std::move(wasm_vm_), vm_id, vm_key, vm_configuration,
envs, allowed_capabilities);
ASSERT_TRUE(wasm_base_->wasm_vm()->load(source, false));
wasm_base_->registerCallbacks();
ASSERT_TRUE(wasm_base_->wasm_vm()->link(""));
}
std::string runtime() { return runtime_; }
proxy_wasm::WasmBase *wasmBase() { return wasm_base_.get(); }
proxy_wasm::WasmVm *wasmVm() { return wasm_vm_ ? wasm_vm_.get() : wasm_base_->wasm_vm(); }
DummyIntegration *integration() { return integration_; }

private:
DummyIntegration *integration_;
std::string source_;
std::string runtime_;
std::unique_ptr<WasmBase> wasm_base_;
std::unique_ptr<proxy_wasm::WasmVm> wasm_vm_;
};
} // namespace proxy_wasm