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

Add WASM standalone builds #137

Open
ajalt opened this issue Sep 3, 2024 · 10 comments
Open

Add WASM standalone builds #137

ajalt opened this issue Sep 3, 2024 · 10 comments

Comments

@ajalt
Copy link

ajalt commented Sep 3, 2024

Thanks for your work on this project!

Would you consider adding building a new wasm-wasi artifact that's compiled with emscripten's standalone mode?

I'm trying to use pdfium in a wasm engine like wasmtime or wasmer which can't use the JS shims that the current wasm build requires.

@paulocoutinhox
Copy link
Owner

Hi,

What is the steps to i understand how to build it.

@ajalt
Copy link
Author

ajalt commented Sep 3, 2024

I was able to get it to build with this:

diff --git a/modules/wasm.py b/modules/wasm.py
--- a/modules/wasm.py
+++ b/modules/wasm.py
@@ -657,6 +657,11 @@ def run_task_generate():
                 "ASSERTIONS=1",
                 "-s",
                 "ALLOW_MEMORY_GROWTH=1",
+                "-sSTANDALONE_WASM=1",
+                "-sWASM_ASYNC_COMPILATION=0",
+                "-sWARN_ON_UNDEFINED_SYMBOLS=1",
+                "-sERROR_ON_UNDEFINED_SYMBOLS=0",
                 "-sMODULARIZE",
                 "-sEXPORT_NAME=PDFiumModule",
                 "-std=c++11",

But I'm not sure if it's better to patch the undefined symbols instead of ignoring them like that.

@paulocoutinhox
Copy link
Owner

We have:

image

What name you suggest for it?

@paulocoutinhox
Copy link
Owner

paulocoutinhox commented Sep 4, 2024

And more two questions:

  • Why we need care about undefined symbols in this case?
  • How i can test the standalone wasm?

@ajalt
Copy link
Author

ajalt commented Sep 4, 2024

The only undefined symbol is __secs_to_zone, which seems to be a MUSL libc function that's used for timezone handling, but that isn't used in the current WASI preview, so it should be fine to ignore.

If you want to call some functions in the library to test, here's an example that uses wasmtime with rust:

use wasmtime::*;
use wasmtime_wasi::*;

fn main() -> Result<(), wasmtime::Error> {
    let engine = Engine::default();
    let module = Module::from_file(&engine, "pdfium.wasm")?;
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |s| s)?;
    linker.define_unknown_imports_as_default_values(&module)?;
    let pre = linker.instantiate_pre(&module)?;
    let wasi_ctx = WasiCtxBuilder::new().build_p1();
    let mut store = Store::new(&engine, wasi_ctx);
    let instance = pre.instantiate(&mut store)?;
    let init_library = instance.get_typed_func::<(), ()>(&mut store, "FPDF_InitLibrary")?;
    init_library.call(&mut store, ())?;
    let get_last_error = instance.get_typed_func::<(), i32>(&mut store, "FPDF_GetLastError")?;
    println!("result={}", get_last_error.call(&mut store, ())?);
    Ok(())
}

wasmtime also has python bindings that should work the same way

@olu-an
Copy link
Contributor

olu-an commented Sep 12, 2024

Hi! Just wanted to follow up on this. I think it would be really helpful.

Thanks for your work on this library!

@paulocoutinhox
Copy link
Owner

Hi,

I have added but get error:
https://github.com/paulocoutinhox/pdfium-lib/pull/140/files

Error:

python3 make.py test-wasmtime                                                                               
Testing with wasmtime...
Traceback (most recent call last):
  File "/Users/paulo/Developer/workspaces/cpp/pdfium-lib/make.py", line 250, in <module>
    main(args)
  File "/Users/paulo/Developer/workspaces/cpp/pdfium-lib/make.py", line 217, in main
    wasm.run_task_test_wasmtime()
  File "/Users/paulo/Developer/workspaces/cpp/pdfium-lib/modules/wasm.py", line 567, in run_task_test_wasmtime
    instance = linker.instantiate(store, module)
  File "/opt/homebrew/lib/python3.10/site-packages/wasmtime/_linker.py", line 171, in instantiate
    raise WasmtimeError._from_ptr(error)
wasmtime._error.WasmtimeError: unknown import: `env::invoke_viii` has not been defined

Do you know what can be wrong?

Thanks.

@ajalt
Copy link
Author

ajalt commented Sep 13, 2024

Thank you for looking into this!

Emscripten adds some functions like that to the compiled WASM file. I'm not sure if it's possible to
stop it from doing that, but it doesn't seem like actually used by pdfium, so it defining them as empty stubs
should work.

In the rust example I posted, that's what linker.define_unknown_imports_as_default_values does.

The python bindings don't look like they have a similar function, but we can do it manually with
something like this:

def define_unknown_imports_as_default_values(linker: Linker, module: Module) -> None:
    for imp in module.imports:
        if imp.module != "env" or not imp.name or not isinstance(imp.type, wasmtime.FuncType):
            continue
        func_ty = imp.type
        result_tys = func_ty.results

        ret = None
        if result_tys:
            ty = result_tys[0]
            if ty in (ValType.f32(), ValType.f64()):
                ret = 0.0
            else:
                ret = 0

        linker.define_func(imp.module, imp.name, func_ty, lambda *_, r=ret: r)

engine = Engine()
module = Module.from_file(engine, "pdfium.wasi.wasm")
store = Store(engine)
linker = Linker(engine)
linker.define_wasi()
wasi_config = WasiConfig()
wasi_config.inherit_stdout()
store.set_wasi(wasi_config)
_define_unknown_imports_as_default_values(linker, module)
instance = linker.instantiate(store, module)
instance.exports(store)["FPDF_InitLibrary"](store)

That's kind of hacky, but it seems to work.

@paulocoutinhox
Copy link
Owner

Nice.

Do you have the commands to test the rust code?

Thanks.

@ajalt
Copy link
Author

ajalt commented Sep 13, 2024

To run that rust code, you'd need a Cargo.toml something like this:

[package]
name = "rust_wasm_test"
version = "0.1.0"
edition = "2021"

[dependencies]
wasmtime = "=24.0.0"
wasmtime-wasi = "=24.0.0"

Put the pdfium.wasm file in the same directory as the Cargo.toml file and add the above rust code to src/main.rs.

Then you'd run cargo build --release to build the binary, and you'd run it with
./target/release/rust_wasm_test.

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

3 participants