Jump to page: 1 2 3
Thread overview
WebAssembly image dithering example
Aug 02, 2018
Allen Garvey
Aug 02, 2018
kinke
Aug 03, 2018
Allen Garvey
Aug 03, 2018
Kagamin
Aug 03, 2018
Radu
Aug 03, 2018
Kagamin
Aug 03, 2018
Radu
Aug 03, 2018
Kagamin
Aug 03, 2018
Radu
Aug 03, 2018
Radu
Aug 03, 2018
Allen Garvey
Aug 03, 2018
kinke
Aug 03, 2018
Allen Garvey
Aug 03, 2018
kinke
Aug 03, 2018
kinke
Aug 03, 2018
Allen Garvey
Aug 03, 2018
kinke
Aug 05, 2018
Allen Garvey
Aug 05, 2018
kinke
Aug 05, 2018
Allen Garvey
Aug 07, 2018
Johan Engelen
August 02, 2018
Hi all. Using the WebAssembly tutorial from a few week ago, I created a small example to dither an image. You can see the source at https://github.com/allen-garvey/wasm-dither-example and the demo at https://allen-garvey.github.io/wasm-dither-example/. I had heard about WebAssembly before this, but this was my first experience actually using it, so it was an interesting project for me, and overall a positive experience.

I also wanted to share some of the challenges I overcame for the benefits of others and I would be grateful if someone more knowledgeable than me can point out if these are the results of my own stupidity, or if they are due to gaps in LDC documentation/implementation or the limits of WebAssembly in general.

(For reference, I was compiling on Kubuntu 18.04 with LDC 1.11.0 beta2)

1. I had to comment out `"-L--no-warn-search-mismatch"` in LDC's `/etc/ldc2.conf` or I would get this error: -d: error: unknown argument: --no-warn-search-mismatch

2. I had to use the disable array bounds checking flag when using arrays or I would get this error: undefined symbol: __assert

3. I was confused for a while when trying to use global arrays or stack allocated arrays since I was getting pointers to garbage. To be fair to LDC, I'm not sure if WebAssembly actually supports doing this, as from my research I'm starting to think all arrays might have to be heap allocated, but it would be nice to get some sort of error or warning if you tried doing this. Also to be fair, array bounds checking was turned off at this point, which might have thrown an exception to let me know something was wrong.

4. I was not able to use any of the optimization flags except for enable inlining, since using any of them would optimize out the entire program. I'm assuming this is because there is no main function, so the compiler can't tell which functions are actually being called. I tried using the `export` or `public` keywords, but that didn't seem to make a difference. Is there some keyword or syntax I'm missing, or is that just a limitation of LDC?

5. When trying to import the std library for the cbrt and max and min function, I got various errors about undefined identifier and such. This wasn't such a big deal in this instance, as the cbrt calculation was a compile time constant, and writing functions to find the min and max values of 3 numbers is not difficult.
August 02, 2018
On Thursday, 2 August 2018 at 22:04:50 UTC, Allen Garvey wrote:
> I also wanted to share some of the challenges I overcame for the benefits of others and I would be grateful if someone more knowledgeable than me can point out if these are the results of my own stupidity, or if they are due to gaps in LDC documentation/implementation or the limits of WebAssembly in general.

Hey, interesting, thx for sharing.

1. Won't be an issue with v1.11 final anymore, it's already fixed in master. Also, you won't need an explicit `-link-internally` anymore.

2. To be expected. Bounds errors trigger assertions, and assertions are redirect to the C assert with `-betterC`. See https://github.com/ldc-developers/ldc/blob/master/tests/baremetal/wasm2.d#L10 for how you can stub it out (or implement it properly).

3. According to a Hello-world Rust tutorial (can't find the link), global pointers (such as a "Hello world" string literal) are relative to the module's linear memory, exposed as something like an ArrayBuffer in exports.memory in JS IIRC. I guess that's all done by LLVM and doesn't need any special handling by LDC, but I haven't tested any of this yet.

4. I'll check it out.

5. To be expected. See https://github.com/ldc-developers/ldc/pull/2787 to get an idea of what'd be required to get parts of druntime and Phobos working for bare-metal targets without C runtime libs.
August 03, 2018
On Thursday, 2 August 2018 at 22:31:40 UTC, kinke wrote:
> 3. According to a Hello-world Rust tutorial (can't find the link), global pointers (such as a "Hello world" string literal) are relative to the module's linear memory, exposed as something like an ArrayBuffer in exports.memory in JS IIRC. I guess that's all done by LLVM and doesn't need any special handling by LDC, but I haven't tested any of this yet.
>
> 4. I'll check it out.
>
> 5. To be expected. See https://github.com/ldc-developers/ldc/pull/2787 to get an idea of what'd be required to get parts of druntime and Phobos working for bare-metal targets without C runtime libs.

Thanks for your response and your offer to look into 4. The links you posted look very helpful, and I will look into stubbing the assert, cbrt and min and max functions with JavaScript, as I found this C++ tutorial https://www.lucidchart.com/techblog/2017/05/16/webassembly-overview-so-fast-so-fun-sorta-difficult/ on how to pass in external functions from JavaScript.

With regards to 3, I found this link https://stackoverflow.com/questions/47529643/how-to-return-a-string-or-similar-from-rust-in-webassembly about passing in a string in Rust, not sure if that is what you were referring to. It seems a bit painful, so I think I'll avoid that for now. Not that I expect you to know, and I don't really know anything about compilers, but since it is possible for the compiler to reserve space on the stack for 4 float variables, I had assumed it would be the same thing if you were using an array of 4 floats, rather than having to manually do everything.

As for 5, I wasn't surprised that it didn't work, but I was sort of naively hoping it would. I don't have any experince with this type of low level programming, so I had thought no garbage collection meant it would run on anything, but I am starting to get my head around how the runtime is separated from the language.
August 03, 2018
Looks like wasm module is sandboxed, so you should marshal anything non-trivial. You can return string pointer (ptr member) and store length in an exported global variable. If the caller reads the stored length right upon receiving the return value, it should be fine.
August 03, 2018
On Thursday, 2 August 2018 at 22:04:50 UTC, Allen Garvey wrote:
> Hi all. Using the WebAssembly tutorial from a few week ago, I created a small example to dither an image. You can see the source at https://github.com/allen-garvey/wasm-dither-example and the demo at https://allen-garvey.github.io/wasm-dither-example/. I had heard about WebAssembly before this, but this was my first experience actually using it, so it was an interesting project for me, and overall a positive experience.
>
> [...]

Cool stuff!

You might wanna change some of the code to be more idiomatic, for example `DitherRCoefficient` can be turned into and enum manifest constant.

As side note, compiler explorer added support for webassembly assembly :) check this out https://godbolt.org/g/PskqaL
August 03, 2018
On Friday, 3 August 2018 at 09:40:30 UTC, Kagamin wrote:
> Looks like wasm module is sandboxed, so you should marshal anything non-trivial. You can return string pointer (ptr member) and store length in an exported global variable. If the caller reads the stored length right upon receiving the return value, it should be fine.

How you pass strings from wasm to js

```wasm.d
extern(C):

void _log(immutable(char)*, size_t);

void log(string msg)
{
    _log(msg.ptr, msg.length);
}

void echo()
{
    log("Helo from WASM!");
}
```

```wasm.js
const request = new XMLHttpRequest();
request.open('GET', 'wasm.wasm');
request.responseType = 'arraybuffer';
request.onload = () => {
  const wasmCode = request.response;
  const importObject = {
      env: {
        _log: (ptr, len) =>
        {
            try
            {
                var buffer = new Uint8Array(linearMemory.buffer, ptr, len);
                var msg = '';
                for (var i=0; i < buffer.length; i++) {
                  msg += String.fromCharCode(buffer[i]);
                }
                console.log(msg);
            }
            catch(err)
            {
              console.log(err);
            }
        }
      }
    };
    var wasmModule = new WebAssembly.Module(wasmCode);
    var wasmInstance = new WebAssembly.Instance(wasmModule, importObject);
    // obtain the module memory
    var linearMemory= wasmInstance.exports.memory;
    wasmInstance.exports.echo();
};
request.send();
```

August 03, 2018
Something like

```wasm.d
extern(C):

static shared size_t len;

const(char)* towastr(string s)
{
    len=s.length;
    return s.ptr;
}

const(char)* echo()
{
    return towastr("Hello from WASM!");
}
```

```wasm.js
...
function wastr(ptr)
{
    var buffer = new Uint8Array(linearMemory.buffer, ptr, wasmInstance.exports.len);
    var msg = '';
    for (var i=0; i < buffer.length; i++) {
        msg += String.fromCharCode(buffer[i]);
    }
    return msg;
}
var str=wastr(wasmInstance.exports.echo());
console.log(str);
```
August 03, 2018
On Friday, 3 August 2018 at 11:34:23 UTC, Kagamin wrote:
> Something like
>
> ```wasm.d
> extern(C):
>
> static shared size_t len;
>
> const(char)* towastr(string s)
> {
>     len=s.length;
>     return s.ptr;
> }
>
> const(char)* echo()
> {
>     return towastr("Hello from WASM!");
> }
> ```
>
> ```wasm.js
> ...
> function wastr(ptr)
> {
>     var buffer = new Uint8Array(linearMemory.buffer, ptr, wasmInstance.exports.len);
>     var msg = '';
>     for (var i=0; i < buffer.length; i++) {
>         msg += String.fromCharCode(buffer[i]);
>     }
>     return msg;
> }
> var str=wastr(wasmInstance.exports.echo());
> console.log(str);
> ```

It wasn't a question :) it was an example, the way I did mine - you pass the length to the Js side, no need to mess with statics.
August 03, 2018
On Friday, 3 August 2018 at 11:42:18 UTC, Radu wrote:
> It wasn't a question :) it was an example, the way I did mine - you pass the length to the Js side, no need to mess with statics.

With your approach the callee must know what will be done with the return value, it's more handy to just have a string and then the caller decides what to do with it.
August 03, 2018
On Friday, 3 August 2018 at 12:01:10 UTC, Kagamin wrote:
> On Friday, 3 August 2018 at 11:42:18 UTC, Radu wrote:
>> It wasn't a question :) it was an example, the way I did mine - you pass the length to the Js side, no need to mess with statics.
>
> With your approach the callee must know what will be done with the return value, it's more handy to just have a string and then the caller decides what to do with it.

Yes, but in that case I think there are better solutions, like returning a `long` that masks ptr and size, you have plenty of room given that ptr is an offset from a predefined chunk o linear memory (thus a small value) and you are also limited in size.

Js will map that to Number from which you can use 56 bits for that ptr+size pair.


« First   ‹ Prev
1 2 3