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

C API execute #533

Closed
wants to merge 3 commits into from
Closed

C API execute #533

wants to merge 3 commits into from

Conversation

gumb0
Copy link
Collaborator

@gumb0 gumb0 commented Sep 17, 2020

Possible options to deal with module-instance relationship.

PR currently uses 2.b approach.

  1. Copy module during instantiate

    FizzyInstance* instance = fizzy_instantiate(module, NULL, 0);
    // can use module, instance has it's own copy
    ...
    fizzy_free_module(module);
    fizzy_free_instance(instance);
    
  2. Invalidate module in instantiate
    2.a. fizzy_free_module required after invalidation, so that any module allocated by fizzy_parse must be freed in the end by the user.

    FizzyInstance* instance = fizzy_instantiate(module, NULL, 0);
    // can't use module
    ...
    fizzy_free_module(module);
    fizzy_free_instance(instance);
    

    2.b. instatiate calls fizzy_free_module, so it must not be called by user afterwards.

    FizzyInstance* instance = fizzy_instantiate(module, NULL, 0);
    // can't use module
    ...
    fizzy_free_instance(instance);
    
  3. Reference counting

    FizzyInstance* instance = fizzy_instantiate(module, NULL, 0);
    // can use module, instance has a reference to the same module
    ...
    fizzy_free_module(module);
    fizzy_free_instance(instance);
    

    Different ways to implement this on the C++ side:
    3.a. double indirection

    struct FizzyModule
    {
        shared_ptr<fizzy::Module> module;
    };
    
    FizzyInstance* fizzy_instantiate(FizzyModule* cmod, ...)
    {
      return fizzy::instantiate(cmod->module, ...)
    }
    

    3.b. Module with enabled shared_from_this:

    class Module : public std::enable_shared_from_this<Module> { ... };
    
    fizzy::Module* unwrap(FizzyModule* cmod) { return reinterpret_cast<fizzy::Module*>(cmod); }
    
    FizzyInstance* fizzy_instantiate(FizzyModule* cmod, ...)
    {
      return fizzy::instantiate(unwrap(cmod)->shared_from_this(), ...)
    }
    

@gumb0 gumb0 force-pushed the capi_execute branch 3 times, most recently from 5def5eb to 7537734 Compare September 21, 2020 09:05
@codecov
Copy link

codecov bot commented Sep 21, 2020

Codecov Report

Merging #533 into master will increase coverage by 0.00%.
The diff coverage is 98.76%.

@@           Coverage Diff            @@
##           master     #533    +/-   ##
========================================
  Coverage   98.23%   98.24%            
========================================
  Files          62       62            
  Lines        9023     9185   +162     
========================================
+ Hits         8864     9024   +160     
- Misses        159      161     +2     

@gumb0 gumb0 force-pushed the capi_execute branch 5 times, most recently from cc3402d to a2d0e67 Compare September 21, 2020 14:01
@axic axic mentioned this pull request Sep 21, 2020
4 tasks
delete instance;
}

fizzy_execution_result fizzy_execute(fizzy_instance* instance, uint32_t func_idx,
Copy link
Member

Choose a reason for hiding this comment

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

I don't think depth should be part of the public API, @chfast ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it must be. My plan is to convert it to a more generic opaque "ThreadExecutionContext". This is a sneak peak: 5f08ee2.

Of course this is up to discussion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, we discussed it a bit when introducing depth in C++ API. My argument for it is that without it host functions don't have an option to respect the call depth limit (calls to imported functions would reset depth to 0). For example, this test would fail (go into infinite recursion):

TEST(execute_call, call_imported_infinite_recursion)

@gumb0 gumb0 force-pushed the capi_execute branch 2 times, most recently from 22ba04d to 7fcde82 Compare September 22, 2020 14:59
@axic axic changed the base branch from master to public_capi September 22, 2020 15:08
@axic
Copy link
Member

axic commented Sep 22, 2020

@gumb0 can you rebase this on #530 ?

@axic
Copy link
Member

axic commented Sep 22, 2020

We need to think about a similar feature as "resolve imports" in C++. wabt/wasm3 have something called"add host function".

@gumb0 gumb0 force-pushed the capi_execute branch 2 times, most recently from e78a350 to f23345a Compare September 23, 2020 14:06
@gumb0 gumb0 force-pushed the capi_execute branch 3 times, most recently from 345af61 to 8ffb157 Compare September 24, 2020 12:53

auto instance = fizzy::instantiate(std::move(module->module), std::move(functions));

auto cinstance = std::make_unique<fizzy_instance>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

In case C++ function returns a pointer, it will be better to just convert it to opaque type pointer.
reinterpret_cast<fizzy_instance*>(instance.release()). This is correct, although looks ugly. You can additionally add wrap/unwrap functions to have ugliness under control.

fizzy_instance* wrap(Instance* x) { return reinterpret_cast<fizzy_instance*>(x); }
Instance* unwrap(fizzy_instance* x) { return reinterpret_cast<instance*>(x); }

Or alternatively,

fizzy_instance* wrap(std::unique_ptr<Instance> x) { return reinterpret_cast<fizzy_instance*>(x.release()); }
std::unique_ptr<Instance> unwrap(fizzy_instance* x) { return {reinterpret_cast<instance*>(x)}; }

The second one looks better to me as it is clear that after unwrap() you own the Instance.

Obviously, the Module is an exception here, so I'm ok with inspecting it some other day.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah this is better. For some reason I thought having Instance& in other parts of C++ API would make this not possible, but now I think it should work.

@@ -4,9 +4,86 @@

#include <fizzy/fizzy.h>

bool dummy(void);
bool validate(const uint8_t* binary, size_t size);
Copy link
Collaborator

Choose a reason for hiding this comment

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

For the future, the "compilation test" is to check if the code in the header is valid C99, C11. The is no need to add code to it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I know, but I can imagine the case when the header compiles, but then calling the function doesn't compile in C like you imagine it would.

Copy link
Collaborator

Choose a reason for hiding this comment

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

There are unittests for that.

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 ones compiled in C99 and C11?

TEST(capi, parse)
{
uint8_t wasm_prefix[]{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00};
auto module = fizzy_parse(wasm_prefix, sizeof(wasm_prefix));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
auto module = fizzy_parse(wasm_prefix, sizeof(wasm_prefix));
auto module = fizzy_parse(wasm_prefix, std::size(wasm_prefix));

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Why is this better?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Counts number of elements, not number of bytes. Here it is the same, so it does not matter.

/// @param args_size Size of the argument array.
/// @param depth Call stack depth.
typedef struct fizzy_execution_result (*fizzy_external_fn)(void* context,
struct fizzy_instance* instance, const union fizzy_value* args, uint32_t args_size, int depth);
Copy link
Collaborator

Choose a reason for hiding this comment

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

The uint32_t will be annoying to use, let's use size_t for array sizes for now (as in fizzy_validate, fizzy_parse, etc).

Suggested change
struct fizzy_instance* instance, const union fizzy_value* args, uint32_t args_size, int depth);
struct fizzy_instance* instance, const union fizzy_value* args, size_t args_size, int depth);

Base automatically changed from public_capi to master September 30, 2020 13:30

/// Free resources associated with the module.
///
/// Should be called only unless @p module was passed to fizzy_instantiate.
Copy link
Member

Choose a reason for hiding this comment

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

Opt 1:

Suggested change
/// Should be called only unless @p module was passed to fizzy_instantiate.
/// Should be called unless @p module was passed to fizzy_instantiate.

Opt 2:

Suggested change
/// Should be called only unless @p module was passed to fizzy_instantiate.
/// Should be called only if @p module was passed to fizzy_instantiate.

/// @param args Pointer to the argument array.
/// @param args_size Size of the argument array.
/// @param depth Call stack depth.
FizzyExecutionResult fizzy_execute(struct FizzyInstance* instance, uint32_t func_idx,
Copy link
Member

Choose a reason for hiding this comment

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

Why is depth signed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In C++ we chose it because it's not defined in the spec, int is a natural type for value involving some arithmetics. Also UBSan can detect overflows for it in tests.

It might be more natural / user-friendly to make it unsigned ot size_t in C.

@axic
Copy link
Member

axic commented Sep 30, 2020

This needs to add something like find_function from utils/fizzy_engine.cpp.

@gumb0 gumb0 force-pushed the capi_execute branch 2 times, most recently from 6871e47 to c517410 Compare October 1, 2020 13:05

/// Pointer to external function.
///
/// @param context Opaque pointer to execution context.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// @param context Opaque pointer to execution context.
/// @param context Opaque pointer to execution context.

///
/// @param context Opaque pointer to execution context.
/// @param instance Pointer to module instance.
/// @param args Pointer to the argument array.
Copy link
Member

Choose a reason for hiding this comment

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

Can args be null?

/// @param context Opaque pointer to execution context.
/// @param instance Pointer to module instance.
/// @param args Pointer to the argument array.
/// @param args_size Size of the argument array.
Copy link
Member

@axic axic Oct 1, 2020

Choose a reason for hiding this comment

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

Suggested change
/// @param args_size Size of the argument array.
/// @param args_size Size of the argument array.

/// Execute module function.
///
/// @param instance Pointer to module instance.
/// @param args Pointer to the argument array.
Copy link
Member

Choose a reason for hiding this comment

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

Can args be null?


extern "C" {
struct FizzyModule
{
fizzy::Module module;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I realize now, that this is not going to work for the case when we want to access already instantiated module, i.e. get FizzyModule* from FizzyInstance* which owns it.

Module has to be dynamically allocated on the C++ side, then on C side module and instance will be represented similarly.

@gumb0 gumb0 marked this pull request as draft October 2, 2020 12:48
@gumb0 gumb0 force-pushed the capi_execute branch 2 times, most recently from dfdbfbf to 6525b9d Compare October 5, 2020 11:17
@gumb0
Copy link
Collaborator Author

gumb0 commented Oct 12, 2020

Closing in favor of #576

@gumb0 gumb0 closed this Oct 12, 2020
@axic axic deleted the capi_execute branch October 12, 2020 13:10
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.

3 participants