-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TObjectDecl #1
Comments
One thing to consider here is |
I once wrote this https://gist.github.com/nadako/f01e6837d5508e922f0ef7287f0520bf. Not sure how well it applies to JVM, but I assume nicolas does something like this in HL too? |
The problem with all these optimization ideas is that this is specified to work: class Main {
static public function main() {
var td = {
a: null
}
trace(Reflect.deleteField(td, "a")); // true
trace(Reflect.deleteField(td, "a")); // false
trace(Reflect.hasField(td, "a")); // false
td.a = null;
trace(Reflect.hasField(td, "a")); // true
trace(Reflect.deleteField(td, "a")); // true
trace(Reflect.deleteField(td, "a")); // false
}
} Note in particular how we go from hasField = false to hasField = true after setting the value again. |
I think it makes sense to unspecify |
To clarify: We'll have to do some bookkeeping anyway in order to support |
That's debatable. In current C#/Java targets, anon objects are made of arrays with binary-search lookup every time. I'm pretty sure that having a proper structure with a setter that sets some bool/bit flag will be faster. |
I think this is also specified: class Main {
static public function main() {
var td:Dynamic = {};
td.a = null;
trace(Reflect.hasField(td, "a")); // true
}
} So it's necessary to have a storage for additional fields on these objects anyway. I think the best we can hope for is a lookup-based general implementation with a fast-path optimization for statically known fields. But even with these we have to be careful because of this deleteField crap... |
Yep, this is actually very similar to |
Here's a Haxe implementation of what I think could work: https://gist.github.com/Simn/f93d4945bcf7991bada5b85b54250565
What do you think? |
This looks a lot like modern JS engines with their hidden classes that switch to the dictionary mode when I think it would be nice to avoid string map for getting/setting known fields. Even if there are ways to avoid it, I'm pretty sure We could generate a switch over known field names for |
Also, Here you meant override function _hx_getKnownFields() {
return ["knownField" => null];
} also why we need to do a copy in |
Yes
We don't. I originally had the map as a static var and forgot to remove the copy(). |
I wonder about this from your design:
That's probably not gonna be enough because we could assign the class to the interface with something inbetween, like Dynamic. Another thing to consider are type parameter constraints. |
I guess this will require some |
I'm not worried about field access because we're gonna have a slow route for that anyway. My concern is the run-time assignment because we would have to make sure that |
Yeah, well one option is to generate class DynamicInterface implements Interface { // actually it has to also implement HxObject itself
public var knownField(get,set):Int;
final __obj:HxObject;
public function new(obj:HxObject) {
this.__obj = obj;
}
function get_knownField():Int return __obj._hx_getField("knownField");
function set_knownField(v:Int):Int return __obj._hx_setField("knownField", v);
} Of course it's technically an extra allocation, but a) if you do that you deserve it and b) it will probably be inlined on stack by Java/JVM anyway. |
No optimizations for known fields yet. see #1
I've added the dynamic version for now. Initialization is somewhat verbose at instruction-level: var obj = {
a: null,
b: 1,
c: "foo"
}
This is gonna be quite efficient though. |
The alternative would be allocating two native arrays, one for names and one for values. That way we would only have 1 set call instead of N, but we would allocate two native arrays and have N store instructions. Plus the set function would then have to iterate on these arrays, which isn't free either. Not sure if that's worth it or not. |
We can improve this in 3 stages: Stage 1: Generate TObjectDecl as class instanceThis should be uncontroversial: Instead of creating a DynamicObject and calling a bunch of We can try to re-use existing data classes for this, but we have to consider evaluation order: The order of the Stage 2: Optimize FAnon access with a checkcastIf we have Stage 3: nadako-interfacesI have to think more about this idea first. I'm a bit concerned about polluting class declarations with lots of possible interface relations to anonymous types. I would like to try + benchmark this at some point though. I wonder if we should consider typedefs at all to identify anons types. Nothing really prevents us from generating a class for This would mean that some truly anonymous types might get misidentified, but I don't think this is an issue as long as we don't assign any semantics to these classes. And even then I don't see what could go wrong with that. |
class Main {
static public function main() {
var obj = getObj();
var stamp = haxe.Timer.stamp();
var target = stamp + 2.0;
var num = 0;
while (haxe.Timer.stamp() < target) {
++num;
call(obj.obj.obj.obj);
}
trace(num);
}
static function call(d:Dynamic) { }
static function getObj() {
return { obj: { obj: { obj: { obj: 12 }}}};
}
} Note that there aren't actually any temp vars, that's just how the decompiler displays this.
|
That would be very nice for readability and native interop :) |
Oh, regarding
My idea was to detect unifications and only add |
But what about |
I guess it would be fair to have a fully-dynamic wrapper implementation for this, like we talked before: #1 (comment) |
Actually, regarding the example code. I think we should have 2 anon classes generated for that nested obj structures: one with generic Object |
Oh it does that, I just didn't public static void main(String[] args) {
Object obj = getObj();
Object var10000 = obj instanceof Anon1 ? ((Anon1)obj).obj : Jvm.readField(obj, "obj");
var10000 = var10000 instanceof Anon1 ? ((Anon1)var10000).obj : Jvm.readField(var10000, "obj");
var10000 = var10000 instanceof Anon1 ? ((Anon1)var10000).obj : Jvm.readField(var10000, "obj");
call(var10000 instanceof Anon2 ? ((Anon2)var10000).obj : Jvm.toInt(Jvm.readField(var10000, "obj")));
} Interestingly, that decompilation is missing the boxing on for the
It goes 72 -> 75 -> 78 -> 81 -> 92 -> 95, and 92 has the boxing. This also makes me realize that the other path does unbox + box (89 + 92). Not sure if there's a good way to fix that though because we need a consistent stack map at the branch join (92 which is reached from 81 (where we have an int) and 89 (where we would have an Integer without the unboxing). |
Addressed the casting in #25:
It now has the |
And for completeness, this is the code when we expect
|
awesome! so clean |
I'm still a bit concerned that the decompilers don't show that cast. IntelliJ just omits it whereas JD simply says |
I went ahead and did the typedef identification thing so we now get nice names: public class Expr extends DynamicObject {
public ExprDef expr;
public Object pos;
protected StringMap _hx_getKnownFields() {
StringMap tmp = new StringMap();
tmp.set("expr", this.expr);
tmp.set("pos", this.pos);
return tmp;
}
public Expr(ExprDef expr, Object pos) {
this.expr = expr;
this.pos = pos;
super();
}
} As I said before, this would also pick up |
nice, why this before super tho? doesn't that supposed to not work? |
Uhm, interesting, I didn't really notice that... Looks like the JVM is fine with this as long as we don't actually call something on |
I think we should not generate an empty |
Indeed |
We now generate proper interfaces, so this is resolved. |
We need a good way to generate anonymous objects.
The text was updated successfully, but these errors were encountered: