2022-09-05 00:53:46 -04:00
|
|
|
#!/usr/bin/env ruby
|
|
|
|
# frozen_string_literal: true
|
2022-09-18 10:25:20 -04:00
|
|
|
|
|
|
|
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('./Gemfile', __dir__)
|
|
|
|
require 'bundler/setup'
|
|
|
|
|
2022-09-05 00:53:46 -04:00
|
|
|
require 'etc'
|
|
|
|
require 'fiddle/import'
|
|
|
|
require 'set'
|
|
|
|
|
2022-09-05 04:06:37 -04:00
|
|
|
unless build_dir = ARGV.first
|
|
|
|
abort "Usage: #{$0} BUILD_DIR"
|
|
|
|
end
|
|
|
|
|
|
|
|
if Fiddle::SIZEOF_VOIDP == 8
|
|
|
|
arch_bits = 64
|
|
|
|
else
|
|
|
|
arch_bits = 32
|
|
|
|
end
|
2022-09-05 00:53:46 -04:00
|
|
|
|
|
|
|
# Help ffi-clang find libclang
|
|
|
|
if arch_bits == 64
|
|
|
|
# apt install libclang1
|
|
|
|
ENV['LIBCLANG'] ||= Dir.glob("/lib/#{RUBY_PLATFORM}-gnu/libclang-*.so*").grep_v(/-cpp/).sort.last
|
|
|
|
else
|
|
|
|
# apt install libclang1:i386
|
|
|
|
ENV['LIBCLANG'] ||= Dir.glob("/lib/i386-linux-gnu/libclang-*.so*").sort.last
|
|
|
|
end
|
|
|
|
require 'ffi/clang'
|
|
|
|
|
|
|
|
class Node < Struct.new(
|
|
|
|
:kind,
|
|
|
|
:spelling,
|
|
|
|
:type,
|
|
|
|
:typedef_type,
|
|
|
|
:bitwidth,
|
|
|
|
:sizeof_type,
|
|
|
|
:offsetof,
|
|
|
|
:enum_value,
|
|
|
|
:children,
|
|
|
|
keyword_init: true,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Parse a C header with ffi-clang and return Node objects.
|
|
|
|
# To ease the maintenance, ffi-clang should be used only inside this class.
|
|
|
|
class HeaderParser
|
|
|
|
def initialize(header, cflags:)
|
2022-09-18 09:44:29 -04:00
|
|
|
@translation_unit = FFI::Clang::Index.new.parse_translation_unit(header, cflags, [], {})
|
2022-09-05 00:53:46 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def parse
|
|
|
|
parse_children(@translation_unit.cursor)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def parse_children(cursor)
|
|
|
|
children = []
|
|
|
|
cursor.visit_children do |cursor, _parent|
|
2022-09-18 09:44:29 -04:00
|
|
|
children << parse_cursor(cursor)
|
2022-09-05 00:53:46 -04:00
|
|
|
next :continue
|
|
|
|
end
|
|
|
|
children
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_cursor(cursor)
|
|
|
|
unless cursor.kind.start_with?('cursor_')
|
|
|
|
raise "unexpected cursor kind: #{cursor.kind}"
|
|
|
|
end
|
|
|
|
kind = cursor.kind.to_s.delete_prefix('cursor_').to_sym
|
|
|
|
children = parse_children(cursor)
|
|
|
|
|
|
|
|
offsetof = {}
|
|
|
|
if kind == :struct
|
|
|
|
children.select { |c| c.kind == :field_decl }.each do |child|
|
|
|
|
offsetof[child.spelling] = cursor.type.offsetof(child.spelling)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
sizeof_type = nil
|
|
|
|
if %i[struct union].include?(kind)
|
|
|
|
sizeof_type = cursor.type.sizeof
|
|
|
|
end
|
|
|
|
|
|
|
|
enum_value = nil
|
|
|
|
if kind == :enum_constant_decl
|
|
|
|
enum_value = cursor.enum_value
|
|
|
|
end
|
|
|
|
|
|
|
|
Node.new(
|
|
|
|
kind: kind,
|
|
|
|
spelling: cursor.spelling,
|
|
|
|
type: cursor.type.spelling,
|
|
|
|
typedef_type: cursor.typedef_type.spelling,
|
|
|
|
bitwidth: cursor.bitwidth,
|
|
|
|
sizeof_type: sizeof_type,
|
|
|
|
offsetof: offsetof,
|
|
|
|
enum_value: enum_value,
|
|
|
|
children: children,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Convert Node objects to a Ruby binding source.
|
|
|
|
class BindingGenerator
|
2022-09-18 10:17:22 -04:00
|
|
|
BINDGEN_BEG = '### MJIT bindgen begin ###'
|
|
|
|
BINDGEN_END = '### MJIT bindgen end ###'
|
2022-09-05 00:53:46 -04:00
|
|
|
DEFAULTS = { '_Bool' => 'CType::Bool.new' }
|
|
|
|
DEFAULTS.default_proc = proc { |_h, k| "CType::Stub.new(:#{k})" }
|
|
|
|
|
|
|
|
attr_reader :src
|
|
|
|
|
2022-09-18 10:17:22 -04:00
|
|
|
# @param src_path [String] Source path used for preamble/postamble
|
2022-09-05 00:53:46 -04:00
|
|
|
# @param macros [Array<String>] Imported macros
|
|
|
|
# @param enums [Hash{ Symbol => Array<String> }] Imported enum values
|
|
|
|
# @param types [Array<String>] Imported types
|
|
|
|
# @param ruby_fields [Hash{ Symbol => Array<String> }] Struct VALUE fields that are considered Ruby objects
|
2022-09-18 10:17:22 -04:00
|
|
|
def initialize(src_path:, macros:, enums:, types:, ruby_fields:)
|
|
|
|
@preamble, @postamble = split_ambles(src_path)
|
2022-09-05 00:53:46 -04:00
|
|
|
@src = String.new
|
|
|
|
@macros = macros.sort
|
|
|
|
@enums = enums.transform_keys(&:to_s).transform_values(&:sort).sort.to_h
|
|
|
|
@types = types.sort
|
|
|
|
@ruby_fields = ruby_fields.transform_keys(&:to_s)
|
|
|
|
@references = Set.new
|
|
|
|
end
|
|
|
|
|
2022-09-18 09:32:21 -04:00
|
|
|
def generate(_nodes)
|
2022-09-18 10:17:22 -04:00
|
|
|
println @preamble
|
2022-09-18 09:44:29 -04:00
|
|
|
|
|
|
|
# Define macros
|
|
|
|
@macros.each do |macro|
|
2022-09-18 10:32:59 -04:00
|
|
|
println " def C.#{macro}"
|
|
|
|
println " #{generate_macro(macro)}"
|
|
|
|
println " end"
|
2022-09-18 09:44:29 -04:00
|
|
|
println
|
|
|
|
end
|
|
|
|
|
2022-09-18 10:17:22 -04:00
|
|
|
print @postamble
|
2022-09-18 09:32:21 -04:00
|
|
|
end
|
|
|
|
|
2022-09-18 09:44:29 -04:00
|
|
|
# TODO: Remove this
|
2022-09-18 09:32:21 -04:00
|
|
|
def legacy_generate(nodes)
|
2022-09-05 00:53:46 -04:00
|
|
|
# TODO: Support nested declarations
|
|
|
|
nodes_index = nodes.group_by(&:spelling).transform_values(&:last)
|
|
|
|
|
|
|
|
println "require_relative 'c_type'"
|
|
|
|
println
|
|
|
|
println "module RubyVM::MJIT"
|
|
|
|
|
|
|
|
# Define enum values
|
|
|
|
@enums.each do |enum, values|
|
|
|
|
values.each do |value|
|
|
|
|
unless definition = generate_enum(nodes_index[enum], value)
|
|
|
|
raise "Failed to generate enum value: #{value}"
|
|
|
|
end
|
|
|
|
println " def C.#{value} = #{definition}"
|
|
|
|
println
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Define types
|
|
|
|
@types.each do |type|
|
|
|
|
unless definition = generate_node(nodes_index[type])
|
|
|
|
raise "Failed to generate type: #{type}"
|
|
|
|
end
|
|
|
|
println " def C.#{type}"
|
|
|
|
println "@#{type} ||= #{definition}".gsub(/^/, " ").chomp
|
|
|
|
println " end"
|
|
|
|
println
|
|
|
|
end
|
|
|
|
|
|
|
|
# Leave a stub for types that are referenced but not targeted
|
|
|
|
(@references - @types).each do |type|
|
|
|
|
println " def C.#{type} = #{DEFAULTS[type]}"
|
|
|
|
println
|
|
|
|
end
|
|
|
|
|
|
|
|
chomp
|
|
|
|
println "end"
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2022-09-18 10:17:22 -04:00
|
|
|
# Return code before BINDGEN_BEG and code after BINDGEN_END
|
|
|
|
def split_ambles(src_path)
|
|
|
|
lines = File.read(src_path).lines
|
|
|
|
|
|
|
|
preamble_end = lines.index { |l| l.include?(BINDGEN_BEG) }
|
|
|
|
raise "`#{BINDGEN_BEG}` was not found in '#{src_path}'" if preamble_end.nil?
|
|
|
|
|
|
|
|
postamble_beg = lines.index { |l| l.include?(BINDGEN_END) }
|
|
|
|
raise "`#{BINDGEN_END}` was not found in '#{src_path}'" if postamble_beg.nil?
|
|
|
|
raise "`#{BINDGEN_BEG}` was found after `#{BINDGEN_END}`" if preamble_end >= postamble_beg
|
|
|
|
|
|
|
|
return lines[0..preamble_end].join, lines[postamble_beg..-1].join
|
|
|
|
end
|
|
|
|
|
2022-09-18 09:44:29 -04:00
|
|
|
def generate_macro(macro)
|
|
|
|
if macro.start_with?('USE_')
|
|
|
|
"Primitive.cexpr! %q{ RBOOL(#{macro} != 0) }"
|
2022-09-05 00:53:46 -04:00
|
|
|
else
|
2022-09-18 09:44:29 -04:00
|
|
|
"Primitive.cexpr! %q{ INT2NUM(#{macro}) }"
|
2022-09-05 00:53:46 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def generate_enum(node, value)
|
|
|
|
case node
|
|
|
|
in Node[kind: :enum_decl, children:]
|
|
|
|
children.find { |c| c.spelling == value }&.enum_value
|
|
|
|
in Node[kind: :typedef_decl, children: [child]]
|
|
|
|
generate_enum(child, value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Generate code from a node. Used for constructing a complex nested node.
|
|
|
|
# @param node [Node]
|
|
|
|
def generate_node(node)
|
|
|
|
case node&.kind
|
|
|
|
when :struct, :union
|
|
|
|
# node.spelling is often empty for union, but we'd like to give it a name when it has one.
|
|
|
|
buf = +"CType::#{node.kind.to_s.sub(/\A[a-z]/, &:upcase)}.new(\n"
|
|
|
|
buf << " \"#{node.spelling}\", #{node.sizeof_type},\n"
|
|
|
|
node.children.each do |child|
|
|
|
|
field_builder = proc do |field, type|
|
|
|
|
if node.kind == :struct
|
|
|
|
to_ruby = @ruby_fields.fetch(node.spelling, []).include?(field)
|
|
|
|
" #{field}: [#{node.offsetof.fetch(field)}, #{type}#{', true' if to_ruby}],\n"
|
|
|
|
else
|
|
|
|
" #{field}: #{type},\n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
case child
|
|
|
|
# BitField is struct-specific. So it must be handled here.
|
|
|
|
in Node[kind: :field_decl, spelling:, bitwidth:, children: [_grandchild]] if bitwidth > 0
|
|
|
|
buf << field_builder.call(spelling, "CType::BitField.new(#{bitwidth}, #{node.offsetof.fetch(spelling) % 8})")
|
|
|
|
# In most cases, we'd like to let generate_type handle the type unless it's "(unnamed ...)".
|
2022-09-05 04:06:37 -04:00
|
|
|
in Node[kind: :field_decl, spelling:, type:] if !type.empty? && !type.match?(/\((unnamed|anonymous) [^)]+\)\z/)
|
2022-09-05 00:53:46 -04:00
|
|
|
buf << field_builder.call(spelling, generate_type(type))
|
|
|
|
# Lastly, "(unnamed ...)" struct and union are handled here, which are also struct-specific.
|
|
|
|
in Node[kind: :field_decl, spelling:, children: [grandchild]]
|
|
|
|
buf << field_builder.call(spelling, generate_node(grandchild).gsub(/^/, ' ').sub(/\A +/, ''))
|
|
|
|
else # forward declarations are ignored
|
|
|
|
end
|
|
|
|
end
|
|
|
|
buf << ")"
|
|
|
|
when :typedef_decl
|
|
|
|
case node.children
|
|
|
|
in [child]
|
|
|
|
generate_node(child)
|
|
|
|
in [child, Node[kind: :integer_literal]]
|
|
|
|
generate_node(child)
|
|
|
|
in _ unless node.typedef_type.empty?
|
|
|
|
generate_type(node.typedef_type)
|
|
|
|
end
|
|
|
|
when :enum_decl
|
|
|
|
generate_type('int')
|
|
|
|
when :type_ref
|
|
|
|
generate_type(node.spelling)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Generate code from a type name. Used for resolving the name of a simple leaf node.
|
|
|
|
# @param type [String]
|
|
|
|
def generate_type(type)
|
|
|
|
if type.match?(/\[\d+\]\z/)
|
|
|
|
return "CType::Pointer.new { #{generate_type(type.sub!(/\[\d+\]\z/, ''))} }"
|
|
|
|
end
|
|
|
|
type = type.delete_suffix('const')
|
|
|
|
if type.end_with?('*')
|
|
|
|
return "CType::Pointer.new { #{generate_type(type.delete_suffix('*').rstrip)} }"
|
|
|
|
end
|
|
|
|
|
|
|
|
type = type.gsub(/((const|volatile) )+/, '').rstrip
|
|
|
|
if type.start_with?(/(struct|union|enum) /)
|
|
|
|
target = type.split(' ', 2).last
|
|
|
|
push_target(target)
|
|
|
|
"self.#{target}"
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
ctype = Fiddle::Importer.parse_ctype(type)
|
|
|
|
"CType::Immediate.new(#{ctype})"
|
|
|
|
rescue Fiddle::DLError
|
|
|
|
push_target(type)
|
|
|
|
"self.#{type}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def print(str)
|
|
|
|
@src << str
|
|
|
|
end
|
|
|
|
|
|
|
|
def println(str = "")
|
|
|
|
@src << str << "\n"
|
|
|
|
end
|
|
|
|
|
|
|
|
def chomp
|
|
|
|
@src.delete_suffix!("\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
def rstrip!
|
|
|
|
@src.rstrip!
|
|
|
|
end
|
|
|
|
|
|
|
|
def push_target(target)
|
|
|
|
unless target.match?(/\A\w+\z/)
|
|
|
|
raise "invalid target: #{target}"
|
|
|
|
end
|
|
|
|
@references << target
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
src_dir = File.expand_path('../..', __dir__)
|
2022-09-18 10:20:44 -04:00
|
|
|
src_path = File.join(src_dir, 'mjit_c.rb')
|
2022-09-05 04:06:37 -04:00
|
|
|
build_dir = File.expand_path(build_dir)
|
2022-09-05 00:53:46 -04:00
|
|
|
cflags = [
|
|
|
|
src_dir,
|
|
|
|
build_dir,
|
|
|
|
File.join(src_dir, 'include'),
|
2022-09-05 04:06:37 -04:00
|
|
|
File.join(build_dir, ".ext/include/#{RUBY_PLATFORM}"),
|
2022-09-05 00:53:46 -04:00
|
|
|
].map { |dir| "-I#{dir}" }
|
|
|
|
|
|
|
|
nodes = HeaderParser.new(File.join(src_dir, 'mjit_compiler.h'), cflags: cflags).parse
|
|
|
|
generator = BindingGenerator.new(
|
2022-09-18 10:17:22 -04:00
|
|
|
src_path: src_path,
|
2022-09-05 00:53:46 -04:00
|
|
|
macros: %w[
|
2022-09-05 04:06:37 -04:00
|
|
|
NOT_COMPILED_STACK_SIZE
|
2022-09-05 00:53:46 -04:00
|
|
|
USE_LAZY_LOAD
|
|
|
|
USE_RVARGC
|
|
|
|
VM_CALL_KW_SPLAT
|
|
|
|
VM_CALL_TAILCALL
|
|
|
|
],
|
|
|
|
enums: {
|
|
|
|
rb_method_type_t: %w[
|
|
|
|
VM_METHOD_TYPE_CFUNC
|
2022-09-05 04:06:37 -04:00
|
|
|
VM_METHOD_TYPE_ISEQ
|
2022-09-05 00:53:46 -04:00
|
|
|
],
|
|
|
|
vm_call_flag_bits: %w[
|
|
|
|
VM_CALL_KW_SPLAT_bit
|
|
|
|
VM_CALL_TAILCALL_bit
|
|
|
|
],
|
|
|
|
},
|
|
|
|
types: %w[
|
2022-09-05 04:06:37 -04:00
|
|
|
CALL_DATA
|
2022-09-05 00:53:46 -04:00
|
|
|
IC
|
|
|
|
IVC
|
|
|
|
RB_BUILTIN
|
|
|
|
VALUE
|
2022-09-05 04:06:37 -04:00
|
|
|
compile_branch
|
2022-09-05 00:53:46 -04:00
|
|
|
compile_status
|
2022-09-05 04:06:37 -04:00
|
|
|
inlined_call_context
|
2022-09-05 00:53:46 -04:00
|
|
|
iseq_inline_constant_cache
|
|
|
|
iseq_inline_constant_cache_entry
|
|
|
|
iseq_inline_iv_cache_entry
|
|
|
|
iseq_inline_storage_entry
|
2022-09-05 04:06:37 -04:00
|
|
|
mjit_options
|
2022-09-05 00:53:46 -04:00
|
|
|
rb_builtin_function
|
2022-09-05 04:06:37 -04:00
|
|
|
rb_call_data
|
|
|
|
rb_callable_method_entry_struct
|
|
|
|
rb_callcache
|
|
|
|
rb_callinfo
|
2022-09-05 00:53:46 -04:00
|
|
|
rb_cref_t
|
2022-09-21 11:58:19 -04:00
|
|
|
rb_control_frame_t
|
2022-09-21 15:27:59 -04:00
|
|
|
rb_execution_context_t
|
|
|
|
rb_execution_context_struct
|
2022-09-05 00:53:46 -04:00
|
|
|
rb_iseq_constant_body
|
2022-09-05 04:06:37 -04:00
|
|
|
rb_iseq_location_t
|
2022-09-05 00:53:46 -04:00
|
|
|
rb_iseq_struct
|
|
|
|
rb_iseq_t
|
|
|
|
rb_iv_index_tbl_entry
|
|
|
|
rb_method_definition_struct
|
|
|
|
rb_method_iseq_t
|
|
|
|
rb_method_type_t
|
2022-09-05 04:06:37 -04:00
|
|
|
rb_mjit_compile_info
|
|
|
|
rb_mjit_unit
|
|
|
|
rb_serial_t
|
2022-09-05 00:53:46 -04:00
|
|
|
],
|
|
|
|
ruby_fields: {
|
|
|
|
rb_iseq_location_struct: %w[
|
|
|
|
base_label
|
|
|
|
first_lineno
|
2022-09-05 04:06:37 -04:00
|
|
|
label
|
|
|
|
pathobj
|
2022-09-05 00:53:46 -04:00
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
generator.generate(nodes)
|
|
|
|
|
2022-09-18 10:17:22 -04:00
|
|
|
File.write(src_path, generator.src)
|