wasmerio / wasmer-ruby

💎🕸 WebAssembly runtime for Ruby
https://wasmer.io
MIT License
471 stars 18 forks source link

Feature request : new classes `Uint64View`, `Int64View`, `Float32View`, and `Float64View` #70

Open kojix2 opened 1 year ago

kojix2 commented 1 year ago

Hi! Thank you for creating very useful library.

Motivation

I am building a tool as a hobby to call WebAssembly functions generated by Crystal language from Ruby to speed up. When I tried to pass a Ruby array to Wasm, I noticed that int64_view, uint64_view, float32_view, and float64_view were not yet provided.

Proposed solution

new methods: uint64_view, int64_view, float32_view, and float64_view new classes Uint64View, Int64View, Float32View, and Float64View

Alternatives

The use of binary strings is a possible alternative. In that case, Ruby methods such as pack and unpack could be used.

Additional context

You can find similar functionality in Python bindings. https://github.com/wasmerio/wasmer-python/commit/b4846547648d92b14f0a2f442c9fb4cf46d8efc7

kojix2 commented 1 year ago

The method mentioned in the "Alternative" section using a binary string with pack and unpack is the method used in wasmtime-rb. These are Memory#read(offset, size) and Memory#write(offset, value). I think this is a very easy-to-use and highly versatile method. Therefore, it might be better to adopt this method rather than adding a new View.

I am a little unsure if the code below is completely correct, but it should work like this.

Write:

      memory.write(
        addr,
        case type
        when 'int8'    then arg.pack('c*')
        when 'uint8'   then arg.pack('C*')
        when 'int16'   then arg.pack('s*')
        when 'uint16'  then arg.pack('S*')
        when 'int32'   then arg.pack('l*')
        when 'uint32'  then arg.pack('L*')
        when 'int64'   then arg.pack('q*')
        when 'uint64'  then arg.pack('Q*')
        when 'float32' then arg.pack('e*')
        when 'float64' then arg.pack('E*')
        else raise "unsupported type: #{type}"
        end
      )

Read:

      case type
      when 'int8'    then memory.read(addr, len * 1).unpack('c*')
      when 'uint8'   then memory.read(addr, len * 1).unpack('C*')
      when 'int16'   then memory.read(addr, len * 2).unpack('s*')
      when 'uint16'  then memory.read(addr, len * 2).unpack('S*')
      when 'int32'   then memory.read(addr, len * 4).unpack('l*')
      when 'uint32'  then memory.read(addr, len * 4).unpack('L*')
      when 'int64'   then memory.read(addr, len * 8).unpack('q*')
      when 'uint64'  then memory.read(addr, len * 8).unpack('Q*')
      when 'float32' then memory.read(addr, len * 4).unpack('e*')
      when 'float64' then memory.read(addr, len * 8).unpack('E*')
      else raise "unsupported type: #{type}"
      end

Of course, the same thing can be done with the current version of wasmer-ruby using uint8_view.

Write:

      uint8_view = memory.uint8_view(addr)
      arg_uint8 = case type
                  when 'int8'    then arg.pack('c*')
                  when 'uint8'   then arg.pack('C*')
                  when 'int16'   then arg.pack('s*')
                  when 'uint16'  then arg.pack('S*')
                  when 'int32'   then arg.pack('l*')
                  when 'uint32'  then arg.pack('L*')
                  when 'int64'   then arg.pack('q*')
                  when 'uint64'  then arg.pack('Q*')
                  when 'float32' then arg.pack('e*')
                  when 'float64' then arg.pack('E*')
                  else raise "unsupported type: #{type}"
                  end.unpack('C*')
      arg_uint8.each_with_index do |a, i|
        uint8_view[i] = a
      end

Read:

      uint8_view = memory.uint8_view(addr)
      case type
      when 'int8'    then Array.new(len * 1) { |i| uint8_view[i] }.pack('C*').unpack('c*')
      when 'uint8'   then Array.new(len * 1) { |i| uint8_view[i] }.pack('C*').unpack('C*')
      when 'int16'   then Array.new(len * 2) { |i| uint8_view[i] }.pack('C*').unpack('s*')
      when 'uint16'  then Array.new(len * 2) { |i| uint8_view[i] }.pack('C*').unpack('S*')
      when 'int32'   then Array.new(len * 4) { |i| uint8_view[i] }.pack('C*').unpack('l*')
      when 'uint32'  then Array.new(len * 4) { |i| uint8_view[i] }.pack('C*').unpack('L*')
      when 'int64'   then Array.new(len * 8) { |i| uint8_view[i] }.pack('C*').unpack('q*')
      when 'uint64'  then Array.new(len * 8) { |i| uint8_view[i] }.pack('C*').unpack('Q*')
      when 'float32' then Array.new(len * 4) { |i| uint8_view[i] }.pack('C*').unpack('e*')
      when 'float64' then Array.new(len * 8) { |i| uint8_view[i] }.pack('C*').unpack('E*')
      else raise "unsupported type: #{type}"
      end