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

Draft implementation of wasm shaper #122

Merged
merged 26 commits into from
Jul 12, 2024
Merged

Draft implementation of wasm shaper #122

merged 26 commits into from
Jul 12, 2024

Conversation

asibahi
Copy link
Contributor

@asibahi asibahi commented Jul 3, 2024

Hello.

working on this

So I started a basic structure of the wasm runner and how to export functions to the wasm module. I am using the wasmtime crate.

The general workflow is this :

  1. look for Wasm table. If it is there : run it.
  2. the shape_with exported function should just always direct to shape_with_plan
  3. if Wasm table is not there or is malformed , use shape_with_plan. instead like normal.

I already hit a couple of things I do not understand:

  1. the API has a concept of scale, which rustybuzz (afaik) does not have. Some of the harfbuzz examples depend on scale. What would be the correct strategy to deal with this?
  2. I have absolutely no idea how to deal with pointers. Using wasmtime APIs to "link" functions with pointers is giving me compiler errors. You can see what the compiler points are by uncommenting the marked lines. Something about traits?

This is a draft PR hoping for some feedback and help.

Final edit I swear:
The two places where I am looking at the functions and how they're used are :

  1. The Design document
  2. The harfbuzz-wasm crate. (yes that's the entire crate.)

The C++ implementation of the wasm api is here But C++ is black magic to me.

Cargo.toml Outdated Show resolved Hide resolved
@LaurenzV
Copy link
Collaborator

LaurenzV commented Jul 3, 2024

the API has a concept of scale, which rustybuzz (afaik) does not have. Some of the harfbuzz examples depend on scale. What would be the correct strategy to deal with this?

How exactly do they depend on it? We indeed don't have scale, so no scale should be applied (or basically a "scale" of 1).

@behdad
Copy link
Member

behdad commented Jul 3, 2024

the API has a concept of scale, which rustybuzz (afaik) does not have. Some of the harfbuzz examples depend on scale. What would be the correct strategy to deal with this?

How exactly do they depend on it? We indeed don't have scale, so no scale should be applied (or basically a "scale" of 1).

Or rather, scale of UPEM in HB terms.

@asibahi
Copy link
Contributor Author

asibahi commented Jul 3, 2024

the API has a concept of scale, which rustybuzz (afaik) does not have. Some of the harfbuzz examples depend on scale. What would be the correct strategy to deal with this?

How exactly do they depend on it? We indeed don't have scale, so no scale should be applied (or basically a "scale" of 1).

I haven't actually looked through the code , but this example uses optical size to render the glyphs.

@behdad
Copy link
Member

behdad commented Jul 3, 2024

This is nice, particularly because wasmtime seems to have a "fuel" concept to limit CPU usage:

https://github.com/bytecodealliance/wasmtime/blob/main/examples/fuel.rs

It generally looks like a better runtime than wasm-micro-runtime, which has several problems as a library.

@asibahi
Copy link
Contributor Author

asibahi commented Jul 3, 2024

the API has a concept of scale, which rustybuzz (afaik) does not have. Some of the harfbuzz examples depend on scale. What would be the correct strategy to deal with this?

How exactly do they depend on it? We indeed don't have scale, so no scale should be applied (or basically a "scale" of 1).

Or rather, scale of UPEM in HB terms.

So I should just return the upem and be done with it? This leaves the issue of how to deal with pointers :D

@behdad
Copy link
Member

behdad commented Jul 3, 2024

the API has a concept of scale, which rustybuzz (afaik) does not have. Some of the harfbuzz examples depend on scale. What would be the correct strategy to deal with this?

How exactly do they depend on it? We indeed don't have scale, so no scale should be applied (or basically a "scale" of 1).

Or rather, scale of UPEM in HB terms.

So I should just return the upem and be done with it? This leaves the issue of how to deal with pointers :D

Yes. UPEM works I think. No idea re pointers I'm afraid.

@RazrFalcon
Copy link
Collaborator

the API has a concept of scale, which rustybuzz (afaik) does not have

Just ignore it.

I have absolutely no idea how to deal with pointers.

Me neither. I never worked with WASM.

@asibahi
Copy link
Contributor Author

asibahi commented Jul 3, 2024

Gave more flesh to a few functions. Left plenty of todo!s tho. I'd appreciate someone looking at them.

For dealing with pointers and wasm memory I got thankfully some pointers (hehe) from @CryZe at the Rust Community Server.

src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
@RazrFalcon
Copy link
Collaborator

A smaller wasm dependency is for sure preferable.

unsafe impl bytemuck::Pod for CBufferContents {}

#[cfg(test)]
mod wasm_tests {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you move tests to the tests directory?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. But I am not sure if that's better for the Ruqaa test, as it now doesn't distinguish between the wasm code failing and faulty output.

Copy link
Collaborator

Choose a reason for hiding this comment

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

How so? Can you elaborate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is not a very well formed thought, but the wasm shaper can fail in two ways (categories of ways, rather). Either shape_with_wasm returns with bad output, or it returns None, because of some wasm failure. Since the tests in the tests folder only test the public API instead of private functions, they always return, as they fall back to the regular shaper. So one might not be able to tell if the wasm module crashed or not.

Cargo.toml Outdated Show resolved Hide resolved
@tschneidereit
Copy link

@asibahi this is very exciting! As a quick note, please don't hesitate to reach out to us in the Bytecode Alliance for any help we might be able to provide with Wasmtime. The Wasmtime maintainers are very responsive on our Zulip.

wasmtime brings Cranelift and all its baggage around. wasmi is much lighter as a dependency and can be compiled to no_std.

Incidentally, we're working on both of those! There's an RFC for adding an interpreter, and recently parts of the runtime gained support for no_std. The latter doesn't yet include Cranelift, but that can be fixed with some elbow grease.

We also have minimal embeddings getting the size of Wasmtime down to about 2MB right now, with a clear path towards the hundreds of kilobytes range if needed.

Having said all this, wasmi is an excellent interpreter, and I'm sure @Robbepop would be excited to help with integrating it, too!

@Robbepop
Copy link

Robbepop commented Jul 9, 2024

Hi, I have just been made aware of this topic by @tschneidereit 's mention of me and haven't read everything (no time to do so rn). I am happy to help. Either solution, Wasmtime or Wasmi, should work fine since both mostly share the same API surface. It could even be possible to use both solutions simultaneously for their different strengths.

src/hb/buffer.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
src/hb/shape.rs Outdated
@@ -20,7 +20,16 @@ pub fn shape(face: &hb_font_t, features: &[Feature], mut buffer: UnicodeBuffer)
buffer.0.language.as_ref(),
features,
);
shape_with_plan(face, &plan, buffer)
#[cfg(not(feature = "wasm-shaper"))]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we check for the Wasm table here? I.e. this should be a runtime decision, not compile-time one.

If we have Wasm table, try shape_with_wasm, otherwise shape_with_plan.

Copy link
Contributor Author

@asibahi asibahi Jul 10, 2024

Choose a reason for hiding this comment

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

This is what this code does. The Wasm table is the first thing shape_with_wasm checks for, and if it is not there it returns None. The compile time check here is for the featuree as shape_with_wasm does not exist then.

ِEdit: I moved it to inside shape_with_plan.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But if there is no Wasm table you exit the shaping immediately, instead of calling the regular shaper. No?
I would expect something like:

if has_wasm_table and run_wasm_shaper():
  return

run_regular_shaper()

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 shape_with_wasm fails if there is no "Wasm" table. The fallback to regular shaper happens in shape_with_plan here. You can see the regular tests pass just fine with the feature turned on.

src/hb/shape.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
@asibahi
Copy link
Contributor Author

asibahi commented Jul 11, 2024

Fixed the bug and now the Ruqaa Font tests works if we do not compare clusters.

That'll teach me to read the standard library's documentation.

src/hb/shape_wasm.rs Outdated Show resolved Hide resolved
src/hb/shape_wasm.rs Show resolved Hide resolved
@asibahi
Copy link
Contributor Author

asibahi commented Jul 11, 2024

One thing I learned about recently:

Is scale from harfbuzz (the one expcted by this API) related to pixels_per_em? the skrifa crate seems to imply as much. If that's so we can perhaps just return the pixels_per_em if set, and if not, return UPEM?

Also re: shape_with , doesn’t rustybuzz technically use the AAT shaper as well as OT?

@RazrFalcon
Copy link
Collaborator

RazrFalcon commented Jul 11, 2024

Is scale from harfbuzz (the one expcted by this API) related to pixels_per_em?

Once again, there is no scale in rustybuzz. It was removed during porting. It should always be 1.

Also re: shape_with , doesn’t rustybuzz technically use the AAT shaper as well as OT?

In harfbuzz terminology this is still the ot shaper. See https://github.com/harfbuzz/harfbuzz/blob/main/src/hb-shaper-list.hh

@behdad
Copy link
Member

behdad commented Jul 11, 2024

Is scale from harfbuzz (the one expcted by this API) related to pixels_per_em?

Once again, there is no scale in rustybuzz. It was removed during porting. It should always be 1.

It should be always units_per_em. And it's NOT related to pixels_per_em.

@asibahi
Copy link
Contributor Author

asibahi commented Jul 11, 2024

So I think this is done.

The full defined API in the design document is not implemented, but only the ones used by the examples.

Edit: should you merge this could you make it a squish merge 😁

@RazrFalcon RazrFalcon merged commit 388d78d into harfbuzz:master Jul 12, 2024
2 checks passed
@RazrFalcon
Copy link
Collaborator

Thanks! Will be released soon. Will see how it will go.

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.

8 participants