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

Imaginary cxx api #1

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft

Imaginary cxx api #1

wants to merge 8 commits into from

Conversation

nlewycky
Copy link
Owner

No description provided.

Copy link

@MarkMcCaskey MarkMcCaskey left a comment

Choose a reason for hiding this comment

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

Looks good to me overall! I didn't fully review changes in wasm.hh and wasm-v8.cc, but it generally seems like it makes the API much more natural in C++

} break;
}
auto operator<<(std::ostream &out, const wasm::Val &val) -> std::ostream& {
std::visit([](auto &&arg) { std::cout << arg; });

Choose a reason for hiding this comment

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

Does this do the same thing? 🤔

Why is it going to std::cout instead of out?

Copy link
Owner Author

Choose a reason for hiding this comment

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

That's a bug, thanks.

std::cout << "> Error accessing export!" << std::endl;
auto run_func = instance.exported_func(0);
if (auto error = run_func.error()) {
std::cout << "> Error accessing export: " << error->what() << std::endl;
exit(1);
}
auto run_func = exports[0]->func();

Choose a reason for hiding this comment

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

redefinition of run_func (previously defined on line 72), is this intentional?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Nope! Bug, thanks.

Comment on lines 15 to 16
intptr_t i = (intptr_t)data;
if (i % (iterations / 10) == 0) printf("Finalizing #%" PRIdPTR "...\n", i);

Choose a reason for hiding this comment

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

Unrelated change, maybe we want to split this off into a separate PR. I'll leave that up to you though!

Copy link
Owner Author

Choose a reason for hiding this comment

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

Yep, unrelated change. Removed!

exit(1);
}
instance->set_host_info(reinterpret_cast<void*>(i), &finalize);
// ?????
instance->set_host_info(&i, &finalize);

Choose a reason for hiding this comment

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

// ?????

Passes ownership of arbitrary data with a destructor to some thing, in this case the instance.

auto set_var_i64_import = get_export_func(exports, i++);
auto set_var_f32_export = get_export_func(exports, i++);
auto set_var_i64_export = get_export_func(exports, i++);
auto const_f32_export = instance->exported_global(i++);

Choose a reason for hiding this comment

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

Is it ever possible to have an exports without an Instance?

Copy link
Owner Author

Choose a reason for hiding this comment

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

No, should it be? I don't think so, but if we should then what would create one?

};
struct EngineConfig {
EngineConfig();
// Implementation provides extra c'tors here.

Choose a reason for hiding this comment

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

How does this happen? By editing this file and shipping their own version of it?

Comment on lines +42 to +43
Module compile(const std::vector<char> &);
Instance instantiate(const std::vector<char> &);

Choose a reason for hiding this comment

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

You know better than I do, but is char the normal way that vectors of bytes are expressed in C++? I know it's correct, just surprised there's not a fancier new type that is commonly used

Copy link
Owner Author

Choose a reason for hiding this comment

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

It is! There is also a newer fancier type named "std::byte" which is absolutely not the right thing to use here. It's safe to say that files in the C++ sense are contiguous chars (or unsigned chars).

Comment on lines +99 to +102
Global &operator=(float);
Global &operator=(double);
Global &operator=(int32_t);
Global &operator=(int64_t);

Choose a reason for hiding this comment

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

Fancy, but how will this handle 128bit numbers and other value types like references? Just make a new type for those things?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Just declare another operator= here, yes.

C++ doesn't have a native 128-bit integer type (well, rather, real compilers for C++ don't uniformly offer 128-bits, the spec specifies intX_t is X bits and lets the implementation pick which to provide). Probably we'll have to define a struct wasm::int128 { uint64_t data[2]; };

};

struct RefVal {
void *ptr = nullptr;

Choose a reason for hiding this comment

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

Curious what your thoughts on this vs Optional<&> or whatever it is in C++

Copy link
Owner Author

Choose a reason for hiding this comment

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

I'm not sure what to do about RefVal until we implement refs. This guarantees that every RefVal object will contain at least one pointer (worth of data) but says nothing about what the pointer is for or what it points to, leaving that up to the implementation choice of the implementer.

I'm not sure why I'd use std::optional here? It could be std::optional<something&> but what's the something?


// Grow memory.
std::cout << "Growing memory..." << std::endl;
check(memory->grow(1), true);
check(memory->size(), 3u);
check(memory->data_size(), 0x30000u);
check(memory->contents()->size(), 0x30000u);

Choose a reason for hiding this comment

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

Hmm, what's the idea here? What is contents, is it type that can be used on its own?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Ideally it'd be a std::span<char> but I realized that std::span didn't make it into c++17.

Thinking about this over the weekend, I now think we should provide a span-compatible type for C++17 users but under a macro that uses just makes our wasm::span = std::span when the user has std::span.

Reference: https://en.cppreference.com/w/cpp/container/span

return nullptr;
int32_t print_callback(int32_t v) {
std::cout << "Calling back..." << std::endl << "> " << v << std::endl;
return v;

Choose a reason for hiding this comment

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

This will prevent returning multi-values I think.

Copy link
Owner Author

Choose a reason for hiding this comment

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

I was thinking multi-value returns would look like this:

std::tuple<int32_t, float> my_callback() {
  return {1, 2.0};
}

which when called from normal C++ would look like:

void foo() {
    auto [x, y] = my_callback();
}

Ideally we want that same syntax when C++ code calls a multi-value return wasm function.

template<typename Fn>
Func exported_func(const std::string &) const;
size_t exported_func_size() const;
bool exported_func_empty() const;

Choose a reason for hiding this comment

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

I'm afraid this will lead to having many methods here:

  • export_func*
  • export_global*
  • export_table*
  • export_mem*

DO they really belong to Instance or should we keep the exports?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Maybe? I'm not convinced that exports makes sense as a standalone object? What does it mean to have an exports but not an instance? (BTW, take a look at https://llvm.org/doxygen/classllvm_1_1Module.html sections "global variable iteration", "function iteration", etc. I was thinking we would do the same thing.)

That said, ExportedFunc has to exist even though it's not really separate from an instance, but we know why we want it to be different, we want the ability to call it.

Starting with C++20 we could use ranges, and code like "for (auto func : instance.exports() | std::views::filter(wasm::Functions)) { " becomes possible. To me that's the most convincing argument for having an exports() function I've thought of.

}
};

wasm::Instance instance = store.instantiate(file, imports);

Choose a reason for hiding this comment

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

I think we should keep the "standard" C++ API free from convenience method and ship them through a wrapper of our own or something like that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants