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

call_tags and engine implementations #7

Open
dbezhetskov opened this issue Oct 26, 2021 · 3 comments
Open

call_tags and engine implementations #7

dbezhetskov opened this issue Oct 26, 2021 · 3 comments

Comments

@dbezhetskov
Copy link

Hi, I'm working on optimization of indirect calls [https://bugzilla.mozilla.org/show_bug.cgi?id=1639153] and happy to leave some comments about call_tags:

  1. let's talk about performance:

calls are efficient (just an additional push, pop, and bitwise equality check compared to a typed function call, which has been evaluated to produce no measurable overhead)

Well, right now it works as you described, with a few low-level details. So, we don't need call_tags here.

modules can restrict how their functions can be called indirectly (e.g. ensure no other module can indirectly call a particular function)

Yes, this is useful, there is an motivational example for C++ -> wasm:

class Base {
public:
    virtual int foo() { return 1; }
};

class Derived : public Base {
public:
    int foo() override { return 2; }
};

int Bar(Base* base) {
    return base->foo();
}

The Bar function will be compiled to call_indirect and wasm engine can't optimize it because call_indirect target can be from different instance or from js [https://godbolt.org/z/41M9rc8jK]. We can optimize this case via call-tags or we can use private tables and do the same stuff via existing mechanism.

When an engine (let it be SpiderMonkey) populates the table with pointers it can deduce what this table is.
If the table has been created in the current module and it hasn't been exported than we can fill the table with raw pointers to the specified functions.
We don't need to switch instances in that case too and so, call_indirect for this case can be as effective as simple call + signature check.
Since we can declare any number of tables we can restrict how our functions will be called via call_indirect.

Could you clarify benefits of call-tags vs tables for this case? @RossTate

call_indirect $table $functype is simply the special case call_with_tag (call_tag.canon $functype) (table.get $table)

table.get $table is really expensive one because we should create JS wrapper for that function as we don't now how we will use that reference - does it flow out to JS or stay in wasm realm?

BTW,

Looking at a function's definition alone, it's not apparent if a function can be indirectly called via call_indirect. One has to do some analysis to check if a reference to that function is made. And once a function reference has been made, it can be very difficult to ensure even something as simple as the function is only accessed by its own module.

In any case we need to track what function is referenced because we need to create some JS wrappers for that function, so this information is already available.

@RossTate
Copy link
Collaborator

Thanks for looking over the proposal and giving feedback, @dbezhetskov! These are great observations and questions. You might want to look at the recent PR #6, which has a bunch of updates, some of which are related to your questions.

  1. let's talk about performance:

calls are efficient (just an additional push, pop, and bitwise equality check compared to a typed function call, which has been evaluated to produce no measurable overhead)

Well, right now it works as you described, with a few low-level details. So, we don't need call_tags here.

Thanks for confirming that's how SpiderMonkey works. Call tags are meant to be a generalization of what you're already doing. Also, we did some experiments and found that, at least for our language that we used call tags for (compiling to LLVM, not WebAssembly), this implementation strategy performs just as well as typed function calls. So that suggests y'all chose a good implementation strategy 😃

Could you clarify benefits of call-tags vs tables for this case? @RossTate

That's a nice optimization you're performing! If you have some numbers on how that affects performance, I'd love to see them. I agree with your observation that the optimization of private tables and private call tags have overlapping benefits in this case. But there are other situations where the private call-tags optimization can be used where the private tables optimization cannot be.

For example, one thing people are doing with C++ WebAssembly modules is splitting them into smaller modules in order to get faster load times. In order to implement C++ dispatch properly, these smaller modules need to all share the same funcref table. That makes the private-table optimization inapplicable. On the other hand, there will be many (sub)classes for which all implementations of and calls to (sub)class methods are in the same smaller module. The module splitter can make these (sub)classes' method implementations and call sites use a call tag that's private to the smaller module. In this way, even though the dispatch is done through a shared table, the engine can still be guaranteed that there's no instance switch.

table.get $table is really expensive one because we should create JS wrapper for that function as we don't now how we will use that reference - does it flow out to JS or stay in wasm realm?

Thanks for confirming this! I had heard conflicting stories here, but this is what I'd inferred from discussions of engine performance. For this reason, I added call_indirect_with_tag in #6.

Looking at a function's definition alone, it's not apparent if a function can be indirectly called via call_indirect. One has to do some analysis to check if a reference to that function is made. And once a function reference has been made, it can be very difficult to ensure even something as simple as the function is only accessed by its own module.

In any case we need to track what function is referenced because we need to create some JS wrappers for that function, so this information is already available.

My comment was from the perspective of a generator wanting to have assurances about limiting unintended backdoors into their program. But your comment does make me realize that I hadn't thought through the JS API implications of this change. My sense is that the funcref derived from a func specifying custom call tags should not be callable from the JS API in order to prevent an unintended backdoor through JS. I'm also realizing func_switch could be used to support interop with overloading in JS, but I'll save that thought for another day.


In #6, there are a bunch of additional applications of call tags, many of which integrate extensions like func_switch and fall-back handlers, and some of which are better matches for the GC proposal rather than tables. Since your engine implementation already aligns with unextended call tags, I would very be interested to hear your thoughts on whether you'd be able to extend your implementation to accommodate these extensions and on what the performance implications would be from the engine's perspective.

@dbezhetskov
Copy link
Author

Hi @RossTate, recently you have asked about performance measurements of improved call_indirect, so, you can find some graphs in the end of the article https://dbezhetskov.dev/blog/opt-ind-call/. TLTR: it is about - 30% for a indirect call to a local function but the optimization has its tradeoff as well.

@RossTate
Copy link
Collaborator

Thanks for directing me to your nice write-up! I'm glad your optimization efforts paid off 😃

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

No branches or pull requests

2 participants