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

Polymorphic host functions based on dynamic trampoline generation. #1217

Merged
merged 29 commits into from
Mar 3, 2020

Conversation

losfair
Copy link
Contributor

@losfair losfair commented Feb 14, 2020

This PR implements polymorphic host functions by dynamically generating the "glue" code that translates platform arguments to an array in runtime-core.

TODO:

  • Multiple return values. Deferring to a future multivalue PR.
  • Dynamic signatures for polymorphic functions.
  • Use a proper executable memory allocator.

@losfair losfair requested review from nlewycky, Hywan and MarkMcCaskey and removed request for nlewycky February 14, 2020 17:39
@losfair
Copy link
Contributor Author

losfair commented Feb 15, 2020

bors try

bors bot added a commit that referenced this pull request Feb 15, 2020
@bors
Copy link
Contributor

bors bot commented Feb 15, 2020

try

Build succeeded

Copy link
Contributor

@Hywan Hywan left a comment

Choose a reason for hiding this comment

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

As discussed in private, I'll incorporate some code form #1018 to make it fit in a more dynamic environment.

lib/runtime-core-tests/tests/imports.rs Show resolved Hide resolved
lib/runtime-core-tests/tests/imports.rs Outdated Show resolved Hide resolved
…ions.

This patch adds a new field in `Func`: `signature`. It contains the
signature of the host function.

For non-polymorphic host functions, the signature is computed from the
`Args` and `Rets` implementation parameters at compile-time.

For polymorphic host functions though, to be fully dynamic, the
signature given to `new_polymorphic` is used in `Func` as the correct
signature.
@Hywan
Copy link
Contributor

Hywan commented Feb 17, 2020

Please, check if #1225 can be included in this PR 🙂.

@losfair
Copy link
Contributor Author

losfair commented Feb 24, 2020

bors try

bors bot added a commit that referenced this pull request Feb 24, 2020
@losfair losfair requested a review from Hywan February 24, 2020 17:20
@bors
Copy link
Contributor

bors bot commented Feb 24, 2020

try

Build succeeded

@losfair losfair marked this pull request as ready for review February 25, 2020 17:46
@losfair
Copy link
Contributor Author

losfair commented Feb 25, 2020

bors try

@losfair
Copy link
Contributor Author

losfair commented Feb 26, 2020

bors try

bors bot added a commit that referenced this pull request Feb 26, 2020
@bors
Copy link
Contributor

bors bot commented Feb 26, 2020

try

Build succeeded

CHANGELOG.md Outdated Show resolved Hide resolved
lib/runtime-core/src/loader.rs Show resolved Hide resolved
lib/runtime-core/src/loader.rs Show resolved Hide resolved
lib/runtime-core/src/trampoline_x64.rs Show resolved Hide resolved
lib/runtime-core/src/trampoline_x64.rs Outdated Show resolved Hide resolved
let mut alloc = self.alloc.lock().unwrap();
let mut found = false;

// Then, try invalidating that assumption...
Copy link
Contributor

Choose a reason for hiding this comment

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

How common is freeing blocks? And inserting new ones?

Should we instead store a single allocated region, plus a list of "free" regions, keeping consecutive free regions coalesced? This loop would then try to allocate out of the free list. We could even keep the free list sorted by size instead of start location, so that we can quickly find the smallest block that will fit?

Should we even bother reusing freed memory here at all? If I understand right, we produce at most one trampoline per imported function, which is a finite number for a given wasm instance? We could have one of these per instance or module, then just allocate off the end only and delete the whole thing when the instance is deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah the free region method would be the ideal way to track dynamic allocation, as in common memory allocators. Here I just kept track of used blocks for simplicity, maybe it's good to change this.

The lifetime of DynamicFunc is unbounded and we cannot associate it to an instance safely at creation time. Or do you think the public API should be refactored and DynamicFunc::new should be made a method on Instance?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was just thinking it'd be faster to run until the end before trying to use up spaces in the middle. The normal case is that this search will return zero free spaces and we don't need to do a scan until we're actually out of memory.

Talked about DynamicFunc::new with Syrus and Mark, we agreed that putting them on Instance is the wrong way to go. Functions can exist outside of instances anyways, and we're currently failing a spectest over that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just implemented it in a simple way so that we would not increase our code's size too much with "yet another allocator". For performance optimization, can we port over an external allocator like wee_alloc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Merging this first and optimizations on the executable memory allocator can be made in another PR.

lib/runtime-core/src/trampoline_x64.rs Show resolved Hide resolved
lib/runtime-core/src/trampoline_x64.rs Outdated Show resolved Hide resolved
lib/runtime-core/src/typed_func.rs Show resolved Hide resolved
lib/runtime-core/src/trampoline_x64.rs Show resolved Hide resolved
lib/runtime-core/src/trampoline_x64.rs Show resolved Hide resolved
let mut alloc = self.alloc.lock().unwrap();
let mut found = false;

// Then, try invalidating that assumption...
Copy link
Contributor

Choose a reason for hiding this comment

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

I was just thinking it'd be faster to run until the end before trying to use up spaces in the middle. The normal case is that this search will return zero free spaces and we don't need to do a scan until we're actually out of memory.

Talked about DynamicFunc::new with Syrus and Mark, we agreed that putting them on Instance is the wrong way to go. Functions can exist outside of instances anyways, and we're currently failing a spectest over that.

lib/runtime-core/src/trampoline_x64.rs Outdated Show resolved Hide resolved
@losfair
Copy link
Contributor Author

losfair commented Mar 3, 2020

bors r+

@bors
Copy link
Contributor

bors bot commented Mar 3, 2020

@bors bors bot merged commit 22d2031 into master Mar 3, 2020
@bors bors bot deleted the feature/polymorphic-v2 branch March 3, 2020 18:25
@tessi
Copy link
Contributor

tessi commented Apr 17, 2020

thanks @losfair for this feature. This was the missing piece for me to implement support for imported functions in my elixir-wrapper. I was about to ask you for this feature, when I spotted it in the changelogs. thanks ❤️

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.

5 participants