From f7d0b6e391c4cada3566e0f0e84b5f5bd8ff2c7a Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Fri, 28 Jun 2024 07:45:10 -0500 Subject: [PATCH 1/2] Intern strings literals that immediately get freeze called --- .../instructions/push_string_instruction.rb | 2 ++ lib/natalie/compiler/pass1.rb | 9 ++++++++- spec/core/string/freeze_spec.rb | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 spec/core/string/freeze_spec.rb diff --git a/lib/natalie/compiler/instructions/push_string_instruction.rb b/lib/natalie/compiler/instructions/push_string_instruction.rb index 67c0b7269..7b926528c 100644 --- a/lib/natalie/compiler/instructions/push_string_instruction.rb +++ b/lib/natalie/compiler/instructions/push_string_instruction.rb @@ -14,6 +14,8 @@ def initialize(string, bytesize: string.bytesize, encoding: Encoding::UTF_8, fro @frozen = frozen end + attr_accessor :frozen + def to_s "push_string #{@string.inspect}, #{@bytesize}, #{@encoding.name}#{@frozen ? ', frozen' : ''}" end diff --git a/lib/natalie/compiler/pass1.rb b/lib/natalie/compiler/pass1.rb index e32abdf3f..8faf67425 100644 --- a/lib/natalie/compiler/pass1.rb +++ b/lib/natalie/compiler/pass1.rb @@ -490,7 +490,14 @@ def transform_call_node(node, used:, with_block: false) if receiver.nil? instructions << PushSelfInstruction.new else - instructions << transform_expression(receiver, used: true) + inst = transform_expression(receiver, used: true) + + if node.name == :freeze && !node.safe_navigation? && !with_block && inst.size == 1 && inst.first.is_a?(PushStringInstruction) + # "foo".freeze get special treatment so we can intern the string at compile time + inst.first.frozen = true + end + + instructions << inst end if node.safe_navigation? diff --git a/spec/core/string/freeze_spec.rb b/spec/core/string/freeze_spec.rb new file mode 100644 index 000000000..2e8e70386 --- /dev/null +++ b/spec/core/string/freeze_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: false +require_relative '../../spec_helper' + +describe "String#freeze" do + + it "produces the same object whenever called on an instance of a literal in the source" do + "abc".freeze.should equal "abc".freeze + end + + it "doesn't produce the same object for different instances of literals in the source" do + "abc".should_not equal "abc" + end + + it "being a special form doesn't change the value of defined?" do + defined?("abc".freeze).should == "method" + end + +end From e7102b28434a44cdc09928343ea24c5f446819d0 Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Fri, 28 Jun 2024 07:45:35 -0500 Subject: [PATCH 2/2] Include interned string in comment This makes it easier to debug the generated cpp. --- lib/natalie/compiler/backends/cpp_backend/transform.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/natalie/compiler/backends/cpp_backend/transform.rb b/lib/natalie/compiler/backends/cpp_backend/transform.rb index 7f73dc1ae..7b0fa9119 100644 --- a/lib/natalie/compiler/backends/cpp_backend/transform.rb +++ b/lib/natalie/compiler/backends/cpp_backend/transform.rb @@ -158,7 +158,7 @@ def intern(symbol) def interned_string(str, encoding) index = @interned_strings[[str, encoding]] ||= @interned_strings.size - "#{interned_strings_var_name}[#{index}]" + "#{interned_strings_var_name}[#{index}]/*#{str.inspect.gsub(%r{\*/|\\}, '?')}*/" end def set_file(file)