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

feat: Linker #87

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

feat: Linker #87

wants to merge 2 commits into from

Conversation

george-cosma
Copy link
Collaborator

@george-cosma george-cosma commented Sep 19, 2024

Pull Request Overview

This pull request adds a "Lookup table" linker. See #83 first.

Basically, after each time a new module is added we attempt to create a "lookup table". This lookup table translate the imported functions from each module to the exported local function in the already-existing modules.

Resumability is kept by passing all module-specific details (like the wasm binary, the readers, function types, store, etc.) to the interpreter_loop::run function. These details have been compiled into a structure called ExecutionInfo.

Also of note, I split the "FunctionInstance" structure into two variants - local and imported, for clarity sake. This comes with the needed modification to the validation process and store-creation process.

Testing Strategy

This pull request was tested by writing a new test, and tested against the existing tests.

Formatting

  • Ran cargo fmt
  • Ran cargo check
  • Ran cargo build
  • Ran cargo doc
  • Ran nix fmt

Github Issue

This pull request closes #83

@george-cosma
Copy link
Collaborator Author

george-cosma commented Sep 24, 2024

Blocked on #88

@george-cosma george-cosma force-pushed the dev/george-linker branch 3 times, most recently from 737c1a5 to 9e29849 Compare September 26, 2024 13:24
@george-cosma george-cosma marked this pull request as ready for review September 26, 2024 13:27
Copy link

codecov bot commented Sep 26, 2024

Codecov Report

Attention: Patch coverage is 90.76087% with 34 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/execution/interpreter_loop.rs 91.20% 11 Missing ⚠️
src/execution/mod.rs 90.75% 3 Missing and 8 partials ⚠️
src/validation/mod.rs 81.57% 7 Missing ⚠️
src/execution/lut.rs 94.00% 2 Missing and 1 partial ⚠️
src/core/error.rs 0.00% 2 Missing ⚠️
Files with missing lines Coverage Δ
src/execution/execution_info.rs 100.00% <100.00%> (ø)
src/execution/store.rs 76.66% <100.00%> (+23.33%) ⬆️
src/execution/value_stack.rs 78.78% <100.00%> (+0.66%) ⬆️
src/validation/code.rs 71.42% <100.00%> (-1.07%) ⬇️
src/core/error.rs 30.88% <0.00%> (-5.49%) ⬇️
src/execution/lut.rs 94.00% <94.00%> (ø)
src/validation/mod.rs 76.51% <81.57%> (+0.23%) ⬆️
src/execution/interpreter_loop.rs 96.58% <91.20%> (-0.47%) ⬇️
src/execution/mod.rs 93.60% <90.75%> (+2.37%) ⬆️

Comment on lines +33 to +42
// TODO: what do we want to do if there is a missing import/export pair? Currently we fail the entire
// operation. Should it be a RuntimeError if said missing pair is called?
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As per internal discussion, "better safe than sorry"

src/execution/lut.rs Show resolved Hide resolved
src/execution/lut.rs Show resolved Hide resolved
src/execution/lut.rs Show resolved Hide resolved

// TODO: how do we handle the start function, if we don't have a LUT yet?
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If a module has a start function, this function will error out if we attempt to run "start" if there are any unmet imports, even if they are not used in "start" itself.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Running an example where we have an import and it's not met in wasm-interp leads to the same error: invalid import. I think this is the safe way to go about it.
(wasm-interp actually errors out even if we don't run any of the code:

(module
  (import "console" "log" (func $log (param i32)))
  (func $dummy
    i32.const 0
    drop 
  )
;;   (start $dummy)
)

)

src/execution/mod.rs Outdated Show resolved Hide resolved
src/execution/mod.rs Outdated Show resolved Hide resolved
src/execution/mod.rs Outdated Show resolved Hide resolved
src/execution/mod.rs Outdated Show resolved Hide resolved
src/execution/mod.rs Outdated Show resolved Hide resolved
Signed-off-by: George Cosma <[email protected]>
@@ -288,20 +357,30 @@ where
let (module_idx, func_idx) =
self.get_indicies(&function_ref.module_name, &function_ref.function_name)?;

if module_idx != function_ref.module_index || func_idx != function_ref.function_index {
// TODO: should we return a different error here?
if module_idx != function_ref.module_index {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe just one if statement here with both conditions combined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The reason I wanted to split them off was to be able to discern which reference is wrong - the module or the function. This was done to conform to the two errors: ModuleNotFound and FunctionNotFound. However, it would also be valid to say there should be a 3rd error type to indicate a stale or incorrect reference

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, that makes sense. I also agree on the 3rd error type for an incorrect reference.

}
})
}
}
Copy link

Choose a reason for hiding this comment

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

This scheme can't handle cases where imports cascade. Let A, B, C be modules where A imports func B.f and rexports A.g, and C import A.g as C.h. When h is called, we need to recursively follow until a local func is hit (which we might not). we might need to check the func_idx is not imported itself here, and if it is, follow the other module without a recursive func call (stackless interpreter rule). We should also throw error if the (module_idx,func_idx) ends up the same as local_module_idx, local_func_idx, since that indicates a cyclic reference. But we can also make do with this too and leave it as an issue.


let wasm_bytes = wat::parse_str(SIMPLE_IMPORT_ADDON).unwrap();
let validation_info = validate(&wasm_bytes).expect("validation failed");
instance.add_module("env", &validation_info);
Copy link

Choose a reason for hiding this comment

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

This a bit nitpicky, but I am more in favor of an interface where if A depends on B's store values for instantiation and B depends on nothing, B is instantiated with a RuntimeInstance::new_named("B") and then we instantiate a with RuntimeInstance::new_named_with("A", instance_of_B/imports??) sort of thing, since instantiation of A might depend on already instantiated const values etc. in B (https://webassembly.github.io/spec/core/exec/modules.html#external-typing) here I think the spec necessitates that the imports are instantiated within their module store. For globals at least this is definitely required, since a global.get of an imported const global can be used in constant exprs during instantiation, and that itself needs to have been instantiated. I think in js module instantiation is also this way, but i am not sure. But this can stay too.

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

Successfully merging this pull request may close these issues.

The never-ending Linker problem
3 participants