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

handle interface functions correctly in component::Linker::func_new #6637

Merged
merged 3 commits into from
Jul 5, 2023

Conversation

dicej
Copy link
Contributor

@dicej dicej commented Jun 23, 2023

Many months ago, I implemented func_new, but only supporting top-level function imports. If you tried to link a host function under an imported interface, it would mistakenly treat it as a top-level function and either error out if it couldn't find a corresponding type definition in the passed &Component; or, if it found a top-level function that happened to have the same name, it would use that type (which would coincidentally work if the type happens to match, but lead to a runtime error later on otherwise).

This fixes the issue by looking up the correct component instance when necessary and getting the type from there.

Note I've made no effort to optimize for performance here. Happy to revisit that if there's a need.

Many months ago, I implemented `func_new`, but only supporting top-level
function imports.  If you tried to link a host function under an imported
interface, it would mistakenly treat it as a top-level function and either error
out if it couldn't find a corresponding type definition in the passed
`&Component`; or, if it found a top-level function that happened to have the
same name, it would use that type (which would coincidentally work if the type
happens to match, but lead to a runtime error later on otherwise).

This fixes the issue by looking up the correct component instance when necessary
and getting the type from there.

Note I've made no effort to optimize for performance here.  Happy to revisit
that if there's a need.

Signed-off-by: Joel Dice <joel.dice@fermyon.com>
@dicej dicej requested a review from a team as a code owner June 23, 2023 22:26
@dicej dicej requested review from jameysharp and removed request for a team June 23, 2023 22:26
@github-actions github-actions bot added the wasmtime:api Related to the API of the `wasmtime` crate itself label Jun 23, 2023
@github-actions
Copy link

Subscribe to Label Action

cc @peterhuene

This issue or pull request has been labeled: "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

  • peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

Copy link
Contributor

@jameysharp jameysharp left a comment

Choose a reason for hiding this comment

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

I've gone ahead and reviewed this because I want to better understand the component-model implementation, but I want to leave final approval to @alexcrichton.

I think this does what you said it should do 👍.

That said, I wonder if you can assign sequential integers to instances, somewhat like Strings::intern does for names. Then a Linker would have a single Vec<NameMap> indexed by this sequential instance ID; Definition::Instance would store this index instead of the full map; and a LinkerInstance would store only its instance index instead of a full path.

If that's feasible, I think it's simpler than the reference-counted singly-linked-list. As a nice bonus, it's also asymptotically faster since func_new can find the right map in constant time instead of linear time.

Comment on lines 345 to 353
if let Some(ty) = map.get(self.strings.strings[name].deref()) {
if let TypeDef::ComponentInstance(index) = ty {
map = &component.types()[*index].exports;
} else {
bail!("import `{name}` has the wrong type (expected a function)");
bail!("import `{name}` has the wrong type (expected a component instance)");
}
} else {
bail!("import `{name}` not found");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should these uses of name all be the string version of the name, rather than the interned usize?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, good catch.

Signed-off-by: Joel Dice <joel.dice@fermyon.com>
Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

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

Seems reasonable to me 👍

I'd be slightly averse though to a custom Rc-based linked list, so I'd lean more towards something Vec like such as this (feel free to take it or leave it though)

diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs
index 6665eccf7..db8daa89d 100644
--- a/crates/wasmtime/src/component/linker.rs
+++ b/crates/wasmtime/src/component/linker.rs
@@ -10,7 +10,6 @@ use std::future::Future;
 use std::marker;
 use std::ops::Deref;
 use std::pin::Pin;
-use std::rc::Rc;
 use std::sync::Arc;
 use wasmtime_environ::component::TypeDef;
 use wasmtime_environ::PrimaryMap;
@@ -25,6 +24,7 @@ pub struct Linker<T> {
     engine: Engine,
     strings: Strings,
     map: NameMap,
+    path: Vec<usize>,
     allow_shadowing: bool,
     _marker: marker::PhantomData<fn() -> T>,
 }
@@ -35,25 +35,15 @@ pub struct Strings {
     strings: Vec<Arc<str>>,
 }
 
-struct PathCell {
-    name: usize,
-    next: Path,
-}
-
-#[derive(Clone)]
-enum Path {
-    Nil,
-    Cell(Rc<PathCell>),
-}
-
 /// Structure representing an "instance" being defined within a linker.
 ///
 /// Instances do not need to be actual [`Instance`]s and instead are defined by
 /// a "bag of named items", so each [`LinkerInstance`] can further define items
 /// internally.
 pub struct LinkerInstance<'a, T> {
-    engine: Engine,
-    path: Path,
+    engine: &'a Engine,
+    path: &'a mut Vec<usize>,
+    path_len: usize,
     strings: &'a mut Strings,
     map: &'a mut NameMap,
     allow_shadowing: bool,
@@ -78,6 +68,7 @@ impl<T> Linker<T> {
             strings: Strings::default(),
             map: NameMap::default(),
             allow_shadowing: false,
+            path: Vec::new(),
             _marker: marker::PhantomData,
         }
     }
@@ -100,8 +91,9 @@ impl<T> Linker<T> {
     /// the root namespace.
     pub fn root(&mut self) -> LinkerInstance<'_, T> {
         LinkerInstance {
-            engine: self.engine.clone(),
-            path: Path::Nil,
+            engine: &self.engine,
+            path: &mut self.path,
+            path_len: 0,
             strings: &mut self.strings,
             map: &mut self.map,
             allow_shadowing: self.allow_shadowing,
@@ -246,8 +238,9 @@ impl<T> Linker<T> {
 impl<T> LinkerInstance<'_, T> {
     fn as_mut(&mut self) -> LinkerInstance<'_, T> {
         LinkerInstance {
-            engine: self.engine.clone(),
-            path: self.path.clone(),
+            engine: self.engine,
+            path: self.path,
+            path_len: self.path_len,
             strings: self.strings,
             map: self.map,
             allow_shadowing: self.allow_shadowing,
@@ -327,13 +320,6 @@ impl<T> LinkerInstance<'_, T> {
         name: &str,
         func: F,
     ) -> Result<()> {
-        let mut names = Vec::new();
-        let mut path = &self.path;
-        while let Path::Cell(cell) = path {
-            names.push(cell.name);
-            path = &cell.next;
-        }
-
         let mut map = &component
             .env_component()
             .import_types
@@ -341,7 +327,7 @@ impl<T> LinkerInstance<'_, T> {
             .map(|(k, v)| (k.clone(), *v))
             .collect::<IndexMap<_, _>>();
 
-        while let Some(name) = names.pop() {
+        for name in self.path.iter().copied().take(self.path_len) {
             let name = self.strings.strings[name].deref();
             if let Some(ty) = map.get(name) {
                 if let TypeDef::ComponentInstance(index) = ty {
@@ -409,10 +395,8 @@ impl<T> LinkerInstance<'_, T> {
             Definition::Instance(map) => map,
             _ => unreachable!(),
         };
-        self.path = Path::Cell(Rc::new(PathCell {
-            name,
-            next: self.path,
-        }));
+        self.path.truncate(self.path_len);
+        self.path.push(name);
         Ok(self)
     }
 

@dicej
Copy link
Contributor Author

dicej commented Jun 24, 2023

Seems reasonable to me +1

I'd be slightly averse though to a custom Rc-based linked list, so I'd lean more towards something Vec like such as this (feel free to take it or leave it though)

Sounds reasonable. I went for the linked list because I assumed there could be more than one sibling LinkerInstance alive at a a given moment and thus couldn't share the same Vec. But now I see that's not possible since it would create aliasing &muts.

Signed-off-by: Joel Dice <joel.dice@fermyon.com>
@dicej
Copy link
Contributor Author

dicej commented Jun 24, 2023

I like @jameysharp's proposal, but Alex's won because it came with a patch :)

@alexcrichton
Copy link
Member

Oh dear I apologize, I saw comments from Jamey and updates from Joel afterwards and I naively didn't actually read the comments and just assumed that the updates handled them! After reading Jamey's comments in more detail I would agree with his suggestion and I think that'd be an excellent refactoring to have. I poked at it for a moment but it was going to get somewhat nontrivial with the matching implementation where the list-of-instances needs to be passed around as context. Nothing insurmountable of course but it might be a bit of a nontrivial refactoring.

I do think though that we won't be able to go to the constant-time implementation of this though because given the current API we always need to walk the path to the current instance in the provided &Component to figure out the type.

@jameysharp how would you feel about deferring your suggestion to an issue? (and/or how do you feel about the current state of the PR as well)

@jameysharp
Copy link
Contributor

I'm happy to defer to your judgement, both of you. I think this isn't performance-critical, right? So making it correct is the only important thing right now, and my impression is that this PR does that as-is.

@alexcrichton
Copy link
Member

Ok sounds good 👍. This isn't performance critical yeah so I'll merge this for now and we can follow-up later as necessary

@alexcrichton alexcrichton added this pull request to the merge queue Jul 5, 2023
Merged via the queue into bytecodealliance:main with commit 624d68b Jul 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wasmtime:api Related to the API of the `wasmtime` crate itself
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants