diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 481bd8a8ee09e3..ee6a7a988f59f0 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -103,7 +103,10 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass, while (1) { if (OSSL_DECODER_from_bio(dctx, bio) == 1) break; - if (ERR_GET_REASON(ERR_peek_error()) != ERR_R_UNSUPPORTED) + // Error queue may not be populated in OpenSSL < 3.0.11 and < 3.1.3 + // https://github.com/openssl/openssl/pull/21603 + unsigned long err = ERR_peek_error(); + if (err && ERR_GET_REASON(err) != ERR_R_UNSUPPORTED) break; if (BIO_eof(bio) == 1) { *retryable = 1; diff --git a/imemo.c b/imemo.c index abf101f362f325..02cba387bd22be 100644 --- a/imemo.c +++ b/imemo.c @@ -51,11 +51,7 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) static rb_imemo_tmpbuf_t * rb_imemo_tmpbuf_new(void) { - size_t size = sizeof(struct rb_imemo_tmpbuf_struct); - VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); - NEWOBJ_OF(obj, struct rb_imemo_tmpbuf_struct, 0, flags, size, 0); - - return obj; + return IMEMO_NEW(rb_imemo_tmpbuf_t, imemo_tmpbuf, 0); } void * diff --git a/internal/imemo.h b/internal/imemo.h index f7bd6202384dca..de39102432ea84 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -93,7 +93,6 @@ struct vm_ifunc { struct rb_imemo_tmpbuf_struct { VALUE flags; - VALUE reserved; VALUE *ptr; /* malloc'ed buffer */ struct rb_imemo_tmpbuf_struct *next; /* next imemo */ size_t cnt; /* buffer size in VALUE */ diff --git a/prism/config.yml b/prism/config.yml index b37b98cbdfe252..3366b6235d003c 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -992,8 +992,19 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the Array + + foo in Bar[] + ^^^ + + foo in Bar[1, 2, 3] + ^^^ + + foo in Bar::Baz[1, 2, 3] + ^^^^^^^^ - name: requireds type: node[] kind: pattern expression @@ -2409,23 +2420,68 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the pattern + + foo in Foo(*bar, baz, *qux) + ^^^ - name: left type: node kind: SplatNode + comment: | + Represents the first wildcard node in the pattern. + + foo in *bar, baz, *qux + ^^^^ + + foo in Foo(*bar, baz, *qux) + ^^^^ - name: requireds type: node[] kind: pattern expression + comment: | + Represents the nodes in between the wildcards. + + foo in *bar, baz, *qux + ^^^ + + foo in Foo(*bar, baz, 1, *qux) + ^^^^^^ - name: right type: node kind: - SplatNode - on error: MissingNode + comment: | + Represents the second wildcard node in the pattern. + + foo in *bar, baz, *qux + ^^^^ + + foo in Foo(*bar, baz, *qux) + ^^^^ - name: opening_loc type: location? + comment: | + The location of the opening brace. + + foo in [*bar, baz, *qux] + ^ + + foo in Foo(*bar, baz, *qux) + ^ - name: closing_loc type: location? + comment: | + The location of the closing brace. + + foo in [*bar, baz, *qux] + ^ + + foo in Foo(*bar, baz, *qux) + ^ comment: | Represents a find pattern in pattern matching. @@ -2437,6 +2493,9 @@ nodes: foo in Foo(*bar, baz, *qux) ^^^^^^^^^^^^^^^^^^^^ + + foo => *bar, baz, *qux + ^^^^^^^^^^^^^^^ - name: FlipFlopNode flags: RangeFlags fields: @@ -2714,20 +2773,60 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the Hash. + + foo => Bar[a: 1, b: 2] + ^^^ + + foo => Bar::Baz[a: 1, b: 2] + ^^^^^^^^ - name: elements type: node[] kind: AssocNode + comment: | + Represents the explicit named hash keys and values. + + foo => { a: 1, b:, ** } + ^^^^^^^^ - name: rest type: node? kind: - AssocSplatNode - NoKeywordsParameterNode + comment: | + Represents the rest of the Hash keys and values. This can be named, unnamed, or explicitly forbidden via `**nil`, this last one results in a `NoKeywordsParameterNode`. + + foo => { a: 1, b:, **c } + ^^^ + + foo => { a: 1, b:, ** } + ^^ + + foo => { a: 1, b:, **nil } + ^^^^^ - name: opening_loc type: location? + comment: | + The location of the opening brace. + + foo => { a: 1 } + ^ + + foo => Bar[a: 1] + ^ - name: closing_loc type: location? + comment: | + The location of the closing brace. + + foo => { a: 1 } + ^ + + foo => Bar[a: 1] + ^ comment: | Represents a hash pattern in pattern matching. @@ -2736,6 +2835,12 @@ nodes: foo => { a: 1, b: 2, **c } ^^^^^^^^^^^^^^^^^^^ + + foo => Bar[a: 1, b: 2] + ^^^^^^^^^^^^^^^ + + foo in { a: 1, b: 2 } + ^^^^^^^^^^^^^^ - name: IfNode fields: - name: if_keyword_loc @@ -3388,6 +3493,9 @@ nodes: foo, bar = baz ^^^ ^^^ + + foo => baz + ^^^ - name: LocalVariableWriteNode fields: - name: name @@ -3478,11 +3586,65 @@ nodes: - name: value type: node kind: non-void expression + comment: | + Represents the left-hand side of the operator. + + foo => bar + ^^^ - name: pattern type: node kind: pattern expression + comment: | + Represents the right-hand side of the operator. The type of the node depends on the expression. + + Anything that looks like a local variable name (including `_`) will result in a `LocalVariableTargetNode`. + + foo => a # This is equivalent to writing `a = foo` + ^ + + Using an explicit `Array` or combining expressions with `,` will result in a `ArrayPatternNode`. This can be preceded by a constant. + + foo => [a] + ^^^ + + foo => a, b + ^^^^ + + foo => Bar[a, b] + ^^^^^^^^^ + + If the array pattern contains at least two wildcard matches, a `FindPatternNode` is created instead. + + foo => *, 1, *a + ^^^^^ + + Using an explicit `Hash` or a constant with square brackets and hash keys in the square brackets will result in a `HashPatternNode`. + + foo => { a: 1, b: } + + foo => Bar[a: 1, b:] + + foo => Bar[**] + + To use any variable that needs run time evaluation, pinning is required. This results in a `PinnedVariableNode` + + foo => ^a + ^^ + + Similar, any expression can be used with pinning. This results in a `PinnedExpressionNode`. + + foo => ^(a + 1) + + Anything else will result in the regular node for that expression, for example a `ConstantReadNode`. + + foo => CONST - name: operator_loc type: location + comment: | + The location of the operator. + + foo => bar + ^^ comment: | Represents the use of the `=>` operator. @@ -3912,12 +4074,32 @@ nodes: - name: expression type: node kind: non-void expression + comment: | + The expression used in the pinned expression + + foo in ^(bar) + ^^^ - name: operator_loc type: location + comment: | + The location of the `^` operator + + foo in ^(bar) + ^ - name: lparen_loc type: location + comment: | + The location of the opening parenthesis. + + foo in ^(bar) + ^ - name: rparen_loc type: location + comment: | + The location of the closing parenthesis. + + foo in ^(bar) + ^ comment: | Represents the use of the `^` operator for pinning an expression in a pattern matching expression. @@ -3936,8 +4118,18 @@ nodes: - NumberedReferenceReadNode # foo in ^$1 - ItLocalVariableReadNode # proc { 1 in ^it } - on error: MissingNode # foo in ^Bar + comment: | + The variable used in the pinned expression + + foo in ^bar + ^^^ - name: operator_loc type: location + comment: | + The location of the `^` operator + + foo in ^bar + ^ comment: | Represents the use of the `^` operator for pinning a variable in a pattern matching expression. diff --git a/prism/prism.c b/prism/prism.c index 2f7ce0b9866438..daf69d2ef97553 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -10834,14 +10834,37 @@ parser_lex(pm_parser_t *parser) { following = next_newline(following, parser->end - following); } - // If the lex state was ignored, or we hit a '.' or a '&.', - // we will lex the ignored newline + // If the lex state was ignored, we will lex the + // ignored newline. + if (lex_state_ignored_p(parser)) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lexed_comment = false; + goto lex_next_token; + } + + // If we hit a '.' or a '&.' we will lex the ignored + // newline. + if (following && ( + (peek_at(parser, following) == '.') || + (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.') + )) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lexed_comment = false; + goto lex_next_token; + } + + + // If we are parsing as CRuby 3.5 or later and we + // hit a '&&' or a '||' then we will lex the ignored + // newline. if ( - lex_state_ignored_p(parser) || - (following && ( - (peek_at(parser, following) == '.') || - (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.') - )) + (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) && + following && ( + (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') || + (peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') || + (peek_at(parser, following) == 'a' && peek_at(parser, following + 1) == 'n' && peek_at(parser, following + 2) == 'd' && !char_is_identifier(parser, following + 3, parser->end - (following + 3))) || + (peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r' && !char_is_identifier(parser, following + 2, parser->end - (following + 2))) + ) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); lexed_comment = false; @@ -10881,6 +10904,63 @@ parser_lex(pm_parser_t *parser) { parser->next_start = NULL; LEX(PM_TOKEN_AMPERSAND_DOT); } + + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) { + // If we hit an && then we are in a logical chain + // and we need to return the logical operator. + if (peek_at(parser, next_content) == '&' && peek_at(parser, next_content + 1) == '&') { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + LEX(PM_TOKEN_AMPERSAND_AMPERSAND); + } + + // If we hit a || then we are in a logical chain and + // we need to return the logical operator. + if (peek_at(parser, next_content) == '|' && peek_at(parser, next_content + 1) == '|') { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + LEX(PM_TOKEN_PIPE_PIPE); + } + + // If we hit an 'and' then we are in a logical chain + // and we need to return the logical operator. + if ( + peek_at(parser, next_content) == 'a' && + peek_at(parser, next_content + 1) == 'n' && + peek_at(parser, next_content + 2) == 'd' && + !char_is_identifier(parser, next_content + 3, parser->end - (next_content + 3)) + ) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 3; + parser->next_start = NULL; + parser->command_start = true; + LEX(PM_TOKEN_KEYWORD_AND); + } + + // If we hit a 'or' then we are in a logical chain + // and we need to return the logical operator. + if ( + peek_at(parser, next_content) == 'o' && + peek_at(parser, next_content + 1) == 'r' && + !char_is_identifier(parser, next_content + 2, parser->end - (next_content + 2)) + ) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + parser->command_start = true; + LEX(PM_TOKEN_KEYWORD_OR); + } + } } // At this point we know this is a regular newline, and we can set the diff --git a/prism/prism.h b/prism/prism.h index a6f22f1a5ad39f..555bda08515fa9 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -49,10 +49,13 @@ PRISM_EXPORTED_FUNCTION const char * pm_version(void); /** * Initialize a parser with the given start and end pointers. * + * The resulting parser must eventually be freed with `pm_parser_free()`. + * * @param parser The parser to initialize. * @param source The source to parse. * @param size The size of the source. - * @param options The optional options to use when parsing. + * @param options The optional options to use when parsing. These options must + * live for the whole lifetime of this parser. */ PRISM_EXPORTED_FUNCTION void pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options); @@ -68,6 +71,9 @@ PRISM_EXPORTED_FUNCTION void pm_parser_register_encoding_changed_callback(pm_par /** * Free any memory associated with the given parser. * + * This does not free the `pm_options_t` object that was used to initialize the + * parser. + * * @param parser The parser to free. */ PRISM_EXPORTED_FUNCTION void pm_parser_free(pm_parser_t *parser); @@ -316,10 +322,10 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * to want to use and be aware of are: * * * `pm_parser_t` - the main parser structure - * * `pm_parser_init` - initialize a parser - * * `pm_parse` - parse and return the root node - * * `pm_node_destroy` - deallocate the root node returned by `pm_parse` - * * `pm_parser_free` - free the internal memory of the parser + * * `pm_parser_init()` - initialize a parser + * * `pm_parse()` - parse and return the root node + * * `pm_node_destroy()` - deallocate the root node returned by `pm_parse()` + * * `pm_parser_free()` - free the internal memory of the parser * * Putting all of this together would look something like: * @@ -336,8 +342,8 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * } * ``` * - * All of the nodes "inherit" from `pm_node_t` by embedding those structures as - * their first member. This means you can downcast and upcast any node in the + * All of the nodes "inherit" from `pm_node_t` by embedding those structures + * as their first member. This means you can downcast and upcast any node in the * tree to a `pm_node_t`. * * @section serializing Serializing @@ -349,9 +355,9 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * use and be aware of are: * * * `pm_buffer_t` - a small buffer object that will hold the serialized AST - * * `pm_buffer_free` - free the memory associated with the buffer - * * `pm_serialize` - serialize the AST into a buffer - * * `pm_serialize_parse` - parse and serialize the AST into a buffer + * * `pm_buffer_free()` - free the memory associated with the buffer + * * `pm_serialize()` - serialize the AST into a buffer + * * `pm_serialize_parse()` - parse and serialize the AST into a buffer * * Putting all of this together would look something like: * @@ -369,7 +375,7 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * @section inspecting Inspecting * * Prism provides the ability to inspect the AST by pretty-printing nodes. You - * can do this with the `pm_prettyprint` function, which you would use like: + * can do this with the `pm_prettyprint()` function, which you would use like: * * ```c * void prettyprint(const uint8_t *source, size_t length) { diff --git a/prism/util/pm_string.h b/prism/util/pm_string.h index f99f1abdf38c9b..ccf6648d263bc0 100644 --- a/prism/util/pm_string.h +++ b/prism/util/pm_string.h @@ -45,11 +45,11 @@ typedef struct { /** This is a slice of another string, and should not be freed. */ PM_STRING_SHARED, - /** This string owns its memory, and should be freed using `pm_string_free`. */ + /** This string owns its memory, and should be freed using `pm_string_free()`. */ PM_STRING_OWNED, #ifdef PRISM_HAS_MMAP - /** This string is a memory-mapped file, and should be freed using `pm_string_free`. */ + /** This string is a memory-mapped file, and should be freed using `pm_string_free()`. */ PM_STRING_MAPPED #endif } type; diff --git a/string.c b/string.c index 7b8a55a5358e7a..8d27248020cded 100644 --- a/string.c +++ b/string.c @@ -5070,34 +5070,36 @@ static VALUE get_pat(VALUE); * match(pattern, offset = 0) -> matchdata or nil * match(pattern, offset = 0) {|matchdata| ... } -> object * - * Returns a MatchData object (or +nil+) based on +self+ and the given +pattern+. - * - * Note: also updates Regexp@Global+Variables. + * Creates a MatchData object based on +self+ and the given arguments; + * updates {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * * - Computes +regexp+ by converting +pattern+ (if not already a Regexp). + * * regexp = Regexp.new(pattern) + * * - Computes +matchdata+, which will be either a MatchData object or +nil+ * (see Regexp#match): - * matchdata = regexp.match(self) * - * With no block given, returns the computed +matchdata+: + * matchdata = regexp.match(self[offset..]) * - * 'foo'.match('f') # => # - * 'foo'.match('o') # => # - * 'foo'.match('x') # => nil - * - * If Integer argument +offset+ is given, the search begins at index +offset+: + * With no block given, returns the computed +matchdata+ or +nil+: * + * 'foo'.match('f') # => # + * 'foo'.match('o') # => # + * 'foo'.match('x') # => nil * 'foo'.match('f', 1) # => nil * 'foo'.match('o', 1) # => # * - * With a block given, calls the block with the computed +matchdata+ - * and returns the block's return value: + * With a block given and computed +matchdata+ non-nil, calls the block with +matchdata+; + * returns the block's return value: * * 'foo'.match(/o/) {|matchdata| matchdata } # => # - * 'foo'.match(/x/) {|matchdata| matchdata } # => nil - * 'foo'.match(/f/, 1) {|matchdata| matchdata } # => nil * + * With a block given and +nil+ +matchdata+, does not call the block: + * + * 'foo'.match(/x/) {|matchdata| fail 'Cannot happen' } # => nil + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -5119,24 +5121,23 @@ rb_str_match_m(int argc, VALUE *argv, VALUE str) * call-seq: * match?(pattern, offset = 0) -> true or false * - * Returns +true+ or +false+ based on whether a match is found for +self+ and +pattern+. + * Returns whether a match is found for +self+ and the given arguments; + * does not update {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * - * Note: does not update Regexp@Global+Variables. + * Computes +regexp+ by converting +pattern+ (if not already a Regexp): * - * Computes +regexp+ by converting +pattern+ (if not already a Regexp). * regexp = Regexp.new(pattern) * - * Returns +true+ if self+.match(regexp) returns a MatchData object, + * Returns +true+ if self[offset..].match(regexp) returns a MatchData object, * +false+ otherwise: * * 'foo'.match?(/o/) # => true * 'foo'.match?('o') # => true * 'foo'.match?(/x/) # => false - * - * If Integer argument +offset+ is given, the search begins at index +offset+: * 'foo'.match?('f', 1) # => false * 'foo'.match?('o', 1) # => true * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -10404,10 +10405,11 @@ rb_str_lstrip_bang(VALUE str) * * whitespace = "\x00\t\n\v\f\r " * s = whitespace + 'abc' + whitespace - * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " - * s.lstrip # => "abc\u0000\t\n\v\f\r " + * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " + * s.lstrip + * # => "abc\u0000\t\n\v\f\r " * - * Related: String#rstrip, String#strip. + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE diff --git a/test/prism/fixtures/leading_logical.txt b/test/prism/fixtures/leading_logical.txt new file mode 100644 index 00000000000000..feb5ee245c8b2f --- /dev/null +++ b/test/prism/fixtures/leading_logical.txt @@ -0,0 +1,21 @@ +1 +&& 2 +&& 3 + +1 +|| 2 +|| 3 + +1 +and 2 +and 3 + +1 +or 2 +or 3 + +1 +andfoo + +2 +orfoo diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 3b4a502b9010f1..124a834317498b 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -25,6 +25,8 @@ class FixturesTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + except << "leading_logical.txt" if RUBY_VERSION < "3.5.0" + Fixture.each(except: except) do |fixture| define_method(fixture.test_name) { assert_valid_syntax(fixture.read) } end diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index d34c3d9dd3cb2f..abce18a0ad3387 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -42,6 +42,9 @@ class LexTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + # https://bugs.ruby-lang.org/issues/20925 + except << "leading_logical.txt" + Fixture.each(except: except) do |fixture| define_method(fixture.test_name) { assert_lex(fixture) } end diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 2a4ea981bdb5c3..129c38a3b5b40d 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -64,6 +64,9 @@ class ParserTest < TestCase # 1.. && 2 "ranges.txt", + + # Cannot yet handling leading logical operators. + "leading_logical.txt", ] # These files contain code that is being parsed incorrectly by the parser diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 5c37178889ecf5..637202487811b5 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -8,6 +8,9 @@ module Prism class RipperTest < TestCase # Skip these tests that Ripper is reporting the wrong results for. incorrect = [ + # Not yet supported. + "leading_logical.txt", + # Ripper incorrectly attributes the block to the keyword. "seattlerb/block_break.txt", "seattlerb/block_next.txt", diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 960e7f63e46385..f4f0f331fb89a9 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -38,6 +38,7 @@ class RubyParserTest < TestCase "dos_endings.txt", "heredocs_with_fake_newlines.txt", "heredocs_with_ignored_newlines.txt", + "leading_logical.txt", "method_calls.txt", "methods.txt", "multi_write.txt", diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index b7e021a4ff4976..bbdeb876ec3eb0 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -2027,10 +2027,11 @@ def obj1.bar(*args, **kws, &block) end obj4 = obj1.clone obj5 = obj1.clone + obj6 = obj1.clone obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) - obj1.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__) obj4.instance_eval("def foo ...\n bar(...)\n""end", __FILE__, __LINE__) obj5.instance_eval("def foo ...; bar(...); end", __FILE__, __LINE__) + obj6.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__) klass = Class.new { def foo(*args, **kws, &block) @@ -2059,7 +2060,7 @@ def obj3.bar(*args, &block) end obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) - [obj1, obj2, obj3, obj4, obj5].each do |obj| + [obj1, obj2, obj3, obj4, obj5, obj6].each do |obj| assert_warning('') { assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x}) } diff --git a/zjit.rb b/zjit.rb index 5e647bfbeeeb89..d6f7a4069267b8 100644 --- a/zjit.rb +++ b/zjit.rb @@ -44,6 +44,7 @@ def stats_string print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20) # Show the most important stats ratio_in_zjit at the end print_counters([ diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 2a581d8baddac8..1f71ad9db3b2b8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -370,7 +370,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio gen_send_without_block(jit, asm, *cd, &function.frame_state(*state)), Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)), &Insn::InvokeSuper { cd, blockiseq, state, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state)), - Insn::InvokeBlock { cd, state, .. } => gen_invoke_block(jit, asm, *cd, &function.frame_state(*state)), + Insn::InvokeBlock { cd, state, .. } => gen_invokeblock(jit, asm, *cd, &function.frame_state(*state)), // Ensure we have enough room fit ec, self, and arguments // TODO remove this check when we have stack args (we can use Time.new to test it) Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state), @@ -981,6 +981,7 @@ fn gen_send( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_send); // Save PC and SP gen_prepare_call_with_gc(asm, state); @@ -1008,6 +1009,7 @@ fn gen_send_without_block( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_send_without_block); // Note that it's incorrect to use this frame state to side exit because // the state might not be on the boundary of an interpreter instruction. @@ -1108,13 +1110,14 @@ fn gen_send_without_block_direct( } /// Compile for invokeblock -fn gen_invoke_block( +fn gen_invokeblock( jit: &mut JITState, asm: &mut Assembler, cd: *const rb_call_data, state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_invokeblock); // Save PC and SP, spill locals and stack gen_prepare_call_with_gc(asm, state); @@ -1141,6 +1144,7 @@ fn gen_invokesuper( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_invokesuper); // Save PC and SP gen_prepare_call_with_gc(asm, state); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 98ddc20226621b..39cfe6a4398fb3 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -142,6 +142,10 @@ make_counters! { // The number of times we do a dynamic dispatch from JIT code dynamic_send_count, + dynamic_send_type_send_without_block, + dynamic_send_type_send, + dynamic_send_type_invokeblock, + dynamic_send_type_invokesuper, } /// Increase a counter by a specified amount