Recently I translated a C++ project to Xojo. To not confuse between property names and variables names, I prefixed a lot of properties with self within a class. This clearly shows that we access a property and not a local variable with the same name. Is this a good idea, what do you think?
Later I was curious whether Xojo's compiler takes the self really just like a scoping indicator or really does some more. A little test project built and then inspected with lldb shows me what is happening:
In the method, the rax is a register containing the reference to the object, named self. That is non nil always in a method as one of the pre-conditions of using methods. If self would be nil, there would be no way to find the right method since all methods in Xojo are virtual ones and go through a dispatch table.
Sample code is like this:
Where a is a property of the class. The disassembler shows, that Xojo compiler does well and just moves the value into the right spot:
a = 1
Now use self.a = 2 and we get this:
0x100133ef0 <+128>: movq $0x1, -0xe8(%rax)
In code the compiler asks the CPU to compare the value in the rax register (containing self) to zero. If that is equal, it jumps to another code block, which raises a NilObjectException!
0x100133f06 <+150>: cmpq $0x0, %rax
0x100133f0a <+154>: je 0x100133f62 ; <+242>
0x100133f0c <+156>: movq -0x8(%rbp), %rax
0x100133f10 <+160>: movq %rax, %rcx
0x100133f13 <+163>: addq $-0xe8, %rcx
0x100133f1a <+170>: movq %rcx, -0x18(%rbp)
0x100133f1e <+174>: movq $0x2, -0xe8(%rax)
0x100133f29 <+185>: movq -0x38(%rbp), %rax
Next it moves rax in a local variable at +156.
In +160 it moves self into rcx, then adds the offset and moves that address to a local variable.
It calculated the offset of the variable, but never uses the offset!
Now at +174 it does the mov of 2 into the property.
Then it restores rax from the local property.
If a is not an integer variable, then it is a bit more complicate with locking and unlocking involved.
Do you think that is optimized away in aggressive compilation mode?
The first line can't really be optimized much:
The register is now r14, but otherwise same.
0x100130c7a <+74>: movq $0x1, -0xe8(%r14)
But the other lines got optimized a bit:
So it still does a nil check, but now with testq instructions instead of cmpq. The compiler is clever. Test only does an bitwise OR and sets flags without changing values. So the jump following is used when the value is nil as the result of the OR is a zero.
0x100130c90 <+96>: testq %r14, %r14
0x100130c93 <+99>: je 0x100130d11 ; <+225>
0x100130c95 <+101>: movq $0x2, -0xe8(%r14)
You know I love Xojo and like it to become better. So what could be done?
Well, first the Xojo compiler could have a little check in there. If we access an object and the compiler creates the nil check, add a new condition. If the object checked is self, just skip the check and continue. This should not cause any harm and remove a couple instructions from every Xojo app.
Even better would be a kind of data check, which is probably already in LLVM. If the same object reference in a local variable is used multiple times, it should only be checked once for nil. LLVM can do this when you flag the variable being non-nil after the nil check. In every assignment or when the parameters come in, you flag them as possible being nil. But it doesn't matter if LLVM is used for the flag or it's a Xojo internal compiler flag.
Anyway, I would love to see this change and I would even volunteer to help on this.
Feedback case is 64102.