Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Convert options into an object
  • Loading branch information
kddnewton committed Nov 23, 2022
commit 4631b5c1708ac71fc53614924ccf1b6155203b94
4 changes: 2 additions & 2 deletions lib/syntax_tree/yarv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ module SyntaxTree
# This module provides an object representation of the YARV bytecode.
module YARV
# Compile the given source into a YARV instruction sequence.
def self.compile(source, **options)
SyntaxTree.parse(source).accept(Compiler.new(**options))
def self.compile(source, options = Compiler::Options.new)
SyntaxTree.parse(source).accept(Compiler.new(options))
end
end
end
92 changes: 58 additions & 34 deletions lib/syntax_tree/yarv/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,53 @@ module YARV
# RubyVM::InstructionSequence.compile("1 + 2").to_a
#
class Compiler < BasicVisitor
# This represents a set of options that can be passed to the compiler to
# control how it compiles the code. It mirrors the options that can be
# passed to RubyVM::InstructionSequence.compile, except it only includes
# options that actually change the behavior.
class Options
def initialize(
frozen_string_literal: false,
inline_const_cache: true,
operands_unification: true,
specialized_instruction: true
)
@frozen_string_literal = frozen_string_literal
@inline_const_cache = inline_const_cache
@operands_unification = operands_unification
@specialized_instruction = specialized_instruction
end

def to_hash
{
frozen_string_literal: @frozen_string_literal,
inline_const_cache: @inline_const_cache,
operands_unification: @operands_unification,
specialized_instruction: @specialized_instruction
}
end

def frozen_string_literal!
@frozen_string_literal = true
end

def frozen_string_literal?
@frozen_string_literal
end

def inline_const_cache?
@inline_const_cache
end

def operands_unification?
@operands_unification
end

def specialized_instruction?
@specialized_instruction
end
end

# This visitor is responsible for converting Syntax Tree nodes into their
# corresponding Ruby structures. This is used to convert the operands of
# some instructions like putobject that push a Ruby object directly onto
Expand Down Expand Up @@ -203,10 +250,7 @@ def visit_unsupported(_node)

# These options mirror the compilation options that we currently support
# that can be also passed to RubyVM::InstructionSequence.compile.
attr_reader :frozen_string_literal,
:inline_const_cache,
:operands_unification,
:specialized_instruction
attr_reader :options

# The current instruction sequence that is being compiled.
attr_reader :iseq
Expand All @@ -216,17 +260,8 @@ def visit_unsupported(_node)
# if we need to return the value of the last statement.
attr_reader :last_statement

def initialize(
frozen_string_literal: false,
inline_const_cache: true,
operands_unification: true,
specialized_instruction: true
)
@frozen_string_literal = frozen_string_literal
@inline_const_cache = inline_const_cache
@operands_unification = operands_unification
@specialized_instruction = specialized_instruction

def initialize(options)
@options = options
@iseq = nil
@last_statement = false
end
Expand All @@ -236,7 +271,7 @@ def visit_BEGIN(node)
end

def visit_CHAR(node)
if frozen_string_literal
if options.frozen_string_literal?
iseq.putobject(node.value[1..])
else
iseq.putstring(node.value[1..])
Expand Down Expand Up @@ -282,7 +317,7 @@ def visit_aref(node)
calldata = YARV.calldata(:[], 1)
visit(node.collection)

if !frozen_string_literal && specialized_instruction &&
if !options.frozen_string_literal? && options.specialized_instruction? &&
(node.index.parts.length == 1)
arg = node.index.parts.first

Expand Down Expand Up @@ -453,7 +488,7 @@ def visit_assign(node)
when ARefField
calldata = YARV.calldata(:[]=, 2)

if !frozen_string_literal && specialized_instruction &&
if !options.frozen_string_literal? && options.specialized_instruction? &&
(node.target.index.parts.length == 1)
arg = node.target.index.parts.first

Expand Down Expand Up @@ -1352,7 +1387,7 @@ def visit_program(node)
break unless statement.is_a?(Comment)

if statement.value == "# frozen_string_literal: true"
@frozen_string_literal = true
options.frozen_string_literal!
end
end

Expand All @@ -1370,18 +1405,7 @@ def visit_program(node)
end
end

top_iseq =
InstructionSequence.new(
:top,
"<compiled>",
nil,
node.location,
frozen_string_literal: frozen_string_literal,
inline_const_cache: inline_const_cache,
operands_unification: operands_unification,
specialized_instruction: specialized_instruction
)

top_iseq = InstructionSequence.new(:top, "<compiled>", nil, node.location, options)
with_child_iseq(top_iseq) do
visit_all(preexes)

Expand All @@ -1402,7 +1426,7 @@ def visit_qsymbols(node)
end

def visit_qwords(node)
if frozen_string_literal
if options.frozen_string_literal?
iseq.duparray(node.accept(RubyVisitor.new))
else
visit_all(node.elements)
Expand Down Expand Up @@ -1632,7 +1656,7 @@ def visit_top_const_ref(node)
end

def visit_tstring_content(node)
if frozen_string_literal
if options.frozen_string_literal?
iseq.putobject(node.accept(RubyVisitor.new))
else
iseq.putstring(node.accept(RubyVisitor.new))
Expand Down Expand Up @@ -1808,7 +1832,7 @@ def visit_word(node)
end

def visit_words(node)
if frozen_string_literal && (compiled = RubyVisitor.compile(node))
if options.frozen_string_literal? && (compiled = RubyVisitor.compile(node))
iseq.duparray(compiled)
else
visit_all(node.elements)
Expand Down
56 changes: 16 additions & 40 deletions lib/syntax_tree/yarv/instruction_sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,9 @@ def change_by(value)
attr_reader :stack

# These are various compilation options provided.
attr_reader :frozen_string_literal,
:inline_const_cache,
:operands_unification,
:specialized_instruction

def initialize(
type,
name,
parent_iseq,
location,
frozen_string_literal: false,
inline_const_cache: true,
operands_unification: true,
specialized_instruction: true
)
attr_reader :options

def initialize(type, name, parent_iseq, location, options = Compiler::Options.new)
@type = type
@name = name
@parent_iseq = parent_iseq
Expand All @@ -105,10 +93,7 @@ def initialize(
@storage_index = 0
@stack = Stack.new

@frozen_string_literal = frozen_string_literal
@inline_const_cache = inline_const_cache
@operands_unification = operands_unification
@specialized_instruction = specialized_instruction
@options = options
end

##########################################################################
Expand Down Expand Up @@ -189,16 +174,7 @@ def to_a
##########################################################################

def child_iseq(type, name, location)
InstructionSequence.new(
type,
name,
self,
location,
frozen_string_literal: frozen_string_literal,
inline_const_cache: inline_const_cache,
operands_unification: operands_unification,
specialized_instruction: specialized_instruction
)
InstructionSequence.new(type, name, self, location, options)
end

def block_child_iseq(location)
Expand Down Expand Up @@ -359,7 +335,7 @@ def getinstancevariable(name)
end

def getlocal(index, level)
if operands_unification
if options.operands_unification?
# Specialize the getlocal instruction based on the level of the
# local variable. If it's 0 or 1, then there's a specialized
# instruction that will look at the current scope or the parent
Expand Down Expand Up @@ -438,11 +414,11 @@ def opt_aset_with(object, calldata)
end

def opt_getconstant_path(names)
if RUBY_VERSION < "3.2" || !inline_const_cache
if RUBY_VERSION < "3.2" || !options.inline_const_cache?
cache = nil
getinlinecache = nil

if inline_const_cache
if options.inline_const_cache?
cache = inline_storage
getinlinecache = opt_getinlinecache(-1, cache)

Expand All @@ -463,7 +439,7 @@ def opt_getconstant_path(names)
getconstant(name)
end

if inline_const_cache
if options.inline_const_cache?
opt_setinlinecache(cache)
getinlinecache.patch!(self)
end
Expand All @@ -477,7 +453,7 @@ def opt_getinlinecache(label, cache)
end

def opt_newarray_max(length)
if specialized_instruction
if options.specialized_instruction?
push(OptNewArrayMax.new(length))
else
newarray(length)
Expand All @@ -486,7 +462,7 @@ def opt_newarray_max(length)
end

def opt_newarray_min(length)
if specialized_instruction
if options.specialized_instruction?
push(OptNewArrayMin.new(length))
else
newarray(length)
Expand All @@ -499,7 +475,7 @@ def opt_setinlinecache(cache)
end

def opt_str_freeze(object)
if specialized_instruction
if options.specialized_instruction?
push(OptStrFreeze.new(object, YARV.calldata(:freeze)))
else
putstring(object)
Expand All @@ -508,7 +484,7 @@ def opt_str_freeze(object)
end

def opt_str_uminus(object)
if specialized_instruction
if options.specialized_instruction?
push(OptStrUMinus.new(object, YARV.calldata(:-@)))
else
putstring(object)
Expand All @@ -525,7 +501,7 @@ def putnil
end

def putobject(object)
if operands_unification
if options.operands_unification?
# Specialize the putobject instruction based on the value of the
# object. If it's 0 or 1, then there's a specialized instruction
# that will push the object onto the stack and requires fewer
Expand Down Expand Up @@ -555,7 +531,7 @@ def putstring(object)
end

def send(calldata, block_iseq = nil)
if specialized_instruction && !block_iseq &&
if options.specialized_instruction? && !block_iseq &&
!calldata.flag?(CallData::CALL_ARGS_BLOCKARG)
# Specialize the send instruction. If it doesn't have a block
# attached, then we will replace it with an opt_send_without_block
Expand Down Expand Up @@ -645,7 +621,7 @@ def setinstancevariable(name)
end

def setlocal(index, level)
if operands_unification
if options.operands_unification?
# Specialize the setlocal instruction based on the level of the
# local variable. If it's 0 or 1, then there's a specialized
# instruction that will write to the current scope or the parent
Expand Down
22 changes: 11 additions & 11 deletions test/compiler_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -428,20 +428,20 @@ class CompilerTest < Minitest::Test

# These are the combinations of instructions that we're going to test.
OPTIONS = [
{},
{ frozen_string_literal: true },
{ operands_unification: false },
{ specialized_instruction: false },
{ inline_const_cache: false },
{ operands_unification: false, specialized_instruction: false }
YARV::Compiler::Options.new,
YARV::Compiler::Options.new(frozen_string_literal: true),
YARV::Compiler::Options.new(operands_unification: false),
YARV::Compiler::Options.new(specialized_instruction: false),
YARV::Compiler::Options.new(inline_const_cache: false),
YARV::Compiler::Options.new(operands_unification: false, specialized_instruction: false)
]

OPTIONS.each do |options|
suffix = options.inspect

CASES.each do |source|
define_method(:"test_#{source}_#{suffix}") do
assert_compiles(source, **options)
assert_compiles(source, options)
end
end
end
Expand Down Expand Up @@ -481,17 +481,17 @@ def serialize_iseq(iseq)
serialized
end

def assert_compiles(source, **options)
def assert_compiles(source, options)
program = SyntaxTree.parse(source)

assert_equal(
serialize_iseq(RubyVM::InstructionSequence.compile(source, **options)),
serialize_iseq(program.accept(YARV::Compiler.new(**options)))
serialize_iseq(program.accept(YARV::Compiler.new(options)))
)
end

def assert_evaluates(expected, source, **options)
assert_equal expected, YARV.compile(source, **options).eval
def assert_evaluates(expected, source)
assert_equal expected, YARV.compile(source).eval
end
end
end