Skip to content

JRuby's impl of FFI differs in behavior from MRI; segfaults/crashes #4413

@ghost

Description

Environment

Environment details:

  • Java HotSpot(TM) 64-Bit Server VM (25.51-b03) for windows-amd64 JRE (1.8.0_51-b16), built on Jun 8 2015 18:03:07 by "java_re" with MS VC++ 10.0 (VS2010)
  • Windows 7 , 64 bit Build 7601 (6.1.7601.23572)
  • JRuby 9.1.6.0

Gems in use:

  • FFI 1.9.14-java

Additional source code:

Expected Behavior

  • The syntax of arg || CONSTANT (evaluates to nil || 0x00 # => 0) should not cause a TypeError.
  • When a Memory Pointer with a length which matches the source ASCII-8Bit -encoded string from which it is derived is passed, a NULL should not be received.
  • Execution of code which operates correctly in Ruby 2.3.3 (MRI) should not greatly differ when executed in JRuby 9k.

Actual Behavior

All observations are currently drawn from the source file environment.rb in the Hephaestus project, a wrapper for Vulkan written in Ruby. Insofar it works as expected on the pre-compiled mingw32-x64 build of ruby-ffi, but the -java derivative is encountering strange behavioral differences.

  • At line 245, from || VK_NULL_HANDLE, which by default translates to nil || 0x00 causes a TypeError ("no implicit conversion of FFI::MemoryPointer to Integer").
  • Likewise at line 318, pCode: bin.to_ptr assignment using the Struct constructor mixin to assign from kwargs causes the following, after which JRuby may segfault or simply crash with an OS-level "application not responding" warning:
ParameterValidation(ERROR): object: 0x0 type: 0 location: 190 msgCode: 4: vkCre
ateShaderModule: required parameter pCreateInfo->pCode specified as NULL
SC(ERROR): object: 0x0 type: 0 location: 10277 msgCode: 5: SPIR-V module not va
lid: Invalid SPIR-V magic number.

Inside the diagnostic output (enabled with -v on the jruby-fixes branch), the reference pCode is clearly identified as being an FFI::MemoryPointer with a real address and a length of 1281 bytes (+1 byte from appending \x00, which is reflected from core FFI behaviors).

The .to_ptr contrivance is another mix-in which allows most basic Ruby objects to be rendered into FFI::MemoryPointer instances by translating their contents. In the above case, it is shorthand for FFI::MemoryPointer.from_string( bin ).

Caveats

Redoing the test without the validation layer causes the crash regardless, making me suspect it is only potentially not an issue with Vulkan (as the SDK is now being bypassed) which is causing the crash. However, I do recall there having been a potential crash in MRI before when I had accidentally passed in the raw GLSL files (as opposed to the correct SPIR-V files).

This does not change the fact that identical syntax between the two implementations of Ruby are not behaving in an identical manner, or that a pointer appears to be shedding or losing its content without warning as though it were being released / GC'd while within the scope of its creation.

JVM Dump

I've captured some findings (replicated above), as well as the output from the JVM when the crash is encountered. You can view it in this gist here.

Test File

I've reproduced the test file below, which uses the raw source of the project (as opposed to the gem, to allow more rapid development). It has not been altered from the code I would use for my personal machine, and thus the adapter name and environment variables may not reflect what you would use.

# modify environment
ENV['PATH'] = './bin;' + ENV['PATH']
ENV['VK_LAYER_PATH'] = 'D:/VulkanSDK/1.0.33.0/Bin'

# load and track time
top = Time.now
require_relative '../vulkan'
require_relative '../lib/utils'
include Vulkan, GLFW3
puts "Loaded Vulkan in #{Time.now - top} seconds"

# mark head
$mark = Time.now
$failed = false

# setup
begin
    puts 'Creating environment ...'
    $env = Vulkan::Environment.new(
        'Test',
        adapter: 'GTX 660',
        validation: 'VK_LAYER_LUNARG_standard_validation'
    )

    puts 'Setting up window ...'
    win = Vulkan::Window.new( 1280, 800, $env )
    puts 'Finishing initialization: '

    puts ' - create rendering path ...'
    $env.create_default_rendering_path

    puts ' - create framebuffers ...'
    $env.create_framebuffers

    puts ' - create commandpool ...'
    $env.create_commandpool

    puts ' - create command buffers ...'
    $env.create_commandbuffers

    puts 'Done.'
rescue Exception => err
    root = Dir.pwd + '/'
    warn "#{err.class.name}: #{err}", *err.backtrace.map {|line| "    " + line.gsub( root, '') }
    $failed = true
ensure
    dT = Time.now - $mark
    puts "Initialization attempt #{$failed ? 'FAILED' : 'succeeded'} after #{dT} seconds",
         "Environment details:",
         "--------------------",
         $env.inspect
end

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions