Is there any optimization for constants?

Hello!
I am facing a little issue with object instances assigned to constants, as apparently the object address keeps changing through the program.

Shortly, my code looks like this:

abstract class Value
end

class ValueA < Value
end

class ValueB < Value
end

def init_a
  return ValueA.new
end

def init_b
  return ValueB.new
end

A = init_a
B = init_b

and I keep referencing A and B through the whole program. What I expect is that the object in both the constants remains the same through the execution. However, checking the addresses it seems that the instance keeps changing overtime as you can see from the below picture

This creates some issue when using the default operator ‘==’.
I wander if there is any compile-time optimization that leads to this behavior.
Does anyone know?

Hi @max-codeware and welcome to the forum!

Your code doesn’t compile (Value is an existing struct). If I change the name, I get the expected behavior: each inspection of A returns the exact same address, so either your program is different or I’m not understanding your problem.

Hi @beta-ziliani,
I wrote the code without considering names, but it was meant to simply explain the logic in a short way. I am a bit confused in front of this issue as I am not able to reproduce it outside my project.
You can check here the constant definition at the bottom of the page: LinCAS/Boolean.cr at feature/core_refactor · LinCAS-lang/LinCAS · GitHub

The code in the branch isn’t compilable yet, as some stuff is missing as I haven’t pushed it yet.
Again, the behavior I wish and expect is the one you saw from compiling the small snipped of code.
However, if you take a look at the picture, I get different addresses at different inspections.

Is it possible that the constant initializer defined by crystal is called multiple times?

May that be that you’re referring to different things? I see there’s a pointerof(<constant>) somewhere. Note that the following two are different:

class A
end

CA = A.new

p CA, pointerof(CA) # => #<A:0x7f8392aede90>   Pointer(A)@0x560c9c784940

No, that should not happen. Initializers are guarded to run only once.

@straight-shoota @beta-ziliani That is what is apparently happening in my code: I added an initializer with a print to console and this was the result

The constant is initialized over and over every time I reference it. I am currently using MacOS. I need some time to see if this happens on Linux as well.
Do you have any idea on how I could inspect this? I tried to reproduce the same error on a separate file, but it worked correctly… I wander if some library may interfere with the single-initialization of the constants. I am compiling my project passing -lclang as link flag if this is of any utility

I think having a small code example that someone could copy/paste/run and see the problem would be quite helpful versus just pasting screenshots of a random program with some output only you are familiar with.

EDIT: Sorry saw this late, but the need for a smaller example is still warranted. Your best bet might just be to keep commenting code out until the error goes away, then undo the last change and try to move the remaining code into a single file to see if it still reproduces, then go from there.

3 Likes

If you’ve installed it via brew, then you could try the interpreter and some good old debugging. Check the Debugging a Program section in Crystal's interpreter – A very special holiday present - The Crystal Programming Language

(Although this might bring other issues… you can also debug more reliably using lldb Debug Crystal in VSCode via CodeLLDB - DEV Community 👩‍💻👨‍💻)

Define a constructor for the class you are getting duplicates for. Add puts caller.join("\n") in that constructor. Then you’ll find out where you are creating these duplicate instances.

I managed to find the criminal code, and “pointerof” mentioned by @beta-ziliani was a cause of it

module M

  class A
    def initialize
      @modified = false
      puts "I'm being created"
    end
    property modified
  end

  def self.create_a
    return A.new
  end
  
  def self.modify_a(a : A*)
    a.value.modified = true
  end

  ConstA = create_a

  puts ConstA
  
  modify_a(pointerof(ConstA))
  
  puts ConstA
  

end

puts M::ConstA

Which produces

I'm being created
I'm being created
#<M::A:0x7f4802a31e90>
I'm being created
I'm being created
#<M::A:0x7f4802a31e70>
I'm being created
#<M::A:0x7f4802a31e60>

The above code practically doesn’t make sense when invoking modify_a with a pointer to an instance. Passing the instance directly is instead more appropriate. This comes from an old version of the program when I used a struct instead of a class, and the two versions mixed up a bit.

However, it is interesting to notice that due to that pointer passing, the constant is initialized multiple times throughout the program. The behavior I expected was that, regardless the pointerof, the address of ConstA would be the same anyways.

If we remove any pointer reference, the program runs correctly.

Is the above code behaving correctly according to the crystal compilation?

1 Like

This seems to be a bug. Apparently, when you take a pointer of a constant the once_init guard loses track of the initialization status.

Reduced reproduction:

Foo = begin
  puts "Initializing Foo"
end

pointerof(Foo)

This prints “Initializing Foo” twice.

I see. @straight-shoota Shall I open a bug in the GitHub repo?

1 Like

Yes, please. That said, I’m very curious as to why you take the pointer of a constant.

Perfect, I will do that shortly.

Regarding the pointer, long story short. the original code was like:

class Obj
end

struct A
  @obj = uninitialized Obj
  property obj
end

ConstA = A.new

def initialize_a(a : A*)
  a.value.obj = Obj.new
end

#... Later in another part of the code

initialize_a(pointerof(ConstA))

The member @obj can be initialized only at runtime, so I created the instance first, and later fully initialized it with the proper object. But I was using a struct, that can’t be passed by reference to a method, so that’s why I was taking the pointer.

I did that with Windows:

lib LibC
  FOLDERID_Profile = GUID.new(0x5e6c858f, 0x0e22, 0x4760, UInt8.static_array(0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73))
end

module Crystal::System::Path
  def self.home : String
    # ...
    elsif LibC.SHGetKnownFolderPath(pointerof(LibC::FOLDERID_Profile), 0, nil, out path_ptr) == 0
      # ...
    end
  end
end

This is not super necessary, and in fact similar code for Windows sockets assigns the constant to a variable (actually method parameter) first, but this should be pretty harmless because the original C example also did it.

Better make that @obj nilable, and use the property! macro.

If you are using uninitialized and pointerof just like that, you are bound to have a really bad time using Crystal. Please avoid using those at all cost! :pray:

3 Likes

Yes. pointerof and uninitialized are explicitly unsafe. Their usage should be restricted to low-level operations related to C bindings or highly optimized algorithms. But they are definitely not good for working around the type system.

If you need pass-by-reference semantics and mutable instance variables, is there a reason not to use a class instead? Pass-by-reference and mutable state are actually 2 of my 3 criteria for choosing whether to use class vs struct — the third being whether I need finalize.

To be clear, I don’t mean to pretend this isn’t a bug. :slightly_smiling_face:

The struct usage was kinda an over optimization of the program. Now I refactored everything using classes.

I wasn’t aware of the property! macro. It is going to make some work much easier :grin: