haxe
haxe copied to clipboard
hl.Ref<> only works for local variables
Calling the _inc function with a local var n:Int = 0 works but if n is not a local variable the result after the _inc call will be unchanged
private function _inc(value:hl.Ref<Int>):Void { value.set(value.get() + 1); }
Full sample:
class Main {
static var a = 10;
static function main() {
inc(a);
trace(a);
}
static function inc(value:hl.Ref<Int>)
value.set(value.get() + 1);
}
That inc call is compiled as
var v = Main.a;
var this1 = $ref(v);
Main.inc(@:implicitCast this1);
And the value of this1 or v is never assigned back to Main.a.
I guess it's an issue of @:semantics(reference) implementation.
Although with php.Ref it works fine. But php.Ref is a typedef while hl.Ref is an abstract.
So, it's something specific to @:semantics(reference) on an abstract.
After some digging, here's the whole reason on why hl.Ref only works with local variables:
In short - it's because how HL generates bytecode.
When you create a reference, you always reference local variable that is created internally. The generated bytecode is as follows (trace omitted):
; Main.hx:5 (Main.main)
r0 i32
r1 $Main
r2 void
r3 ref(i32)
.5 @0 global 1, 4
.5 @1 field 0,1[5]
.5 @2 ref 3,&0
.5 @3 call 2, Main.inc(3)
And HLC output (again, clutter from trace omitted):
$Main r1;
int *r3;
int r0;
r1 = ($Main)g$_Main; // instruction 0
r0 = r1->a; // instruction 1 and main culprit of the issue
r3 = &r0; // Creation of reference to local variable, not the variable we expect
Main_inc(r3); // We pass reference to local variable.
As you can see, the issue is clear-cut: The way code is generated literally cannot reference non-local variables. Moreso, changing the referenced type to anything that is a pointer (i.e. passed by reference) would not even compile for HLC and crash with a very non-descriptive Invalid_argument("output_value: functional value"), meaning that in HLC you can only reference primitive types that are passed by value. On HL you can create ref to the pass-by-reference objects and it works just fine.
I hesitate to call it a bug, as it's more a design flaw, but I suppose simplest way to fix the issue is to merge field and ref opcodes to something like fieldref opcode (or just omit field altogether if it's possible with current ref opcode):
r0 $Main
r1 void
r2 ref(i32)
.5 @0 global 0, 4
.5 @1 fieldref 2,&0[5]
.5 @2 call 1, Main.inc(2)