Controlling Inling in GWT compiler

112 views
Skip to first unread message

Peter Donald

unread,
Oct 17, 2017, 8:58:23 PM10/17/17
to GWT Mailing List
Hi,

So I am trying to figure out a way to control inlining in GWT compiler and potentially if it will be compatible with J2CL. My motivation is that I want to build type-safe builders that are compiled away at runtime. The idea is to compile something like
new InputBuilder().type( InputType.checkbox ).className( "foo" ).child( "Hello" ).build()
to
React.createElement('input', { className: 'foo' }, 'Hello')
And browsing the code I thought I could make use of @ForceInline and @HasNoSideEffects in appropriate places to control the inlining. The goal would essentially be to move most of the building code into caller which allow the data flow analyzer to zap it into a nice compact form. But I can not seem to get it to work.

It is unclear whether I am using it wrong or it just won't work and reading through the compiler source code is proving to be slow way to understand the problem. 

@ForceInline does not seem to force inlining but instead raises an error if code is not inlined? Am I understanding this correctly? Certainly adding the annotation to all the places that I wanted that occur causes a error like "Function X is marked as @ForceInline but it could not be inlined". Reading through the JsInliner source code has not enlightened me why.

So more code less talk. Here is the example I have been working with


Next question is around optimisation of the new jsinterop code. It seems that GWT compiler does not work particularly well and something like
final JsPropertyMapOfAny prop = JsPropertyMap.of();
prop.set( "a", 1 );
prop.set( "b", 2 );
prop.set( "c", 3 );
will not produce an object literal.
So do you think this is an achievable goal with GWT2, what about in J2CL? Any suggestions on how I could achieve this?
BTW I know using things like @ForceInline and @HasNoSideEffects is "dangerous" and unsupported but I am comfortable with the impact I would have on compile size/time. (Once worked on JikesRVM which is a JVM written in Java which has similar annotations and similar tradeoffs - see http://www.jikesrvm.org/JavaDoc/org/vmmagic/pragma/package-summary.html)

--
Cheers,

Peter Donald

Colin Alworth

unread,
Oct 17, 2017, 10:14:37 PM10/17/17
to GWT Users
For the first one, chaining methods has the unfortunate detail that each method has a return value, which the next object is called on. To my knowledge, there is no optimization in the compiler which currently can undo the presence of the return value, and recognize that the method returns "this" (or a param, some other easily aliased value) as a special case.

The canonical example I like for this is StringBuilder - three main methods we care about, so it is easier to understand in an example. Each is followed by its compiled JS
constructor: new A(), does something like this.str = "";
append: object.b(string), or $b(static$this, string) when made static. Both have an impl that does this.str+=string (or this$static.str+=string, which is of course shorter when compiled)
toString(): object.c(), or c(static$this) when made static. Returns this.str, or static$this.str.

Example code:
String s = new StringBuilder().append("1").append(name).append("2").toString()
Trivial JS impl:
var s=new A().b("1").b(name).b("2").c();
Actual JS impl, once methods have been made final and devirtualized (made static, since there are no overrides possible here):
var s=c(b(b(b(new A),"1"),name),"2");
"Inlined" implementation (ignoring the trivial string rewrite, as your example would also still have to be separate statements):
var t=new A;t.str+="1";t.str+=name;t.str+="2";var s=t.str;

Lets assume that "str" was only one char, since we are compiling after all, 40 chars for the "idiomatic" js version, 37 chars for staticified version, 50 for the "inlined" version. Granted, we have to pay the costs of those static/inlined versions existing at all, but if your method is called more than once, it becomes worth it. So I would dispute "compact" form, at least without more detail - a chained "setter" of form a(b,c) is going to be shorter than b.a=c; by one character (the semicolon, or newline), and can itself be inlined into another expression, while the "field assignment" can't be.

Until you get to object literal creation - which can't be optimized for normal java objects (for reasons I don't fully understand, assume it has to do with possible side effects in clinits). Remember too that the GWT compiler is mostly geared toward Java objects, where calling super-super-superclass constructors, run all initializers and clinits while they are at it. A non-issue in JS, I'll admit, so GWT hasn't been geared toward this historically. Closure on the other hand...

https://closure-compiler.appspot.com/home#code%3D%252F%252F%2520%253D%253DClosureCompiler%253D%253D%250A%252F%252F%2520%2540compilation_level%2520ADVANCED_OPTIMIZATIONS%250A%252F%252F%2520%2540output_file_name%2520default.js%250A%252F%252F%2520%253D%253D%252FClosureCompiler%253D%253D%250A%250A%252F%252F%2520ADD%2520YOUR%2520CODE%2520HERE%250Avar%2520a%2520%253D%2520%257B%257D%253B%250A%250Aa.a%2520%253D%2520false%250Aa.b%2520%253D%2520%27asdf%27%253B%250Aa.c%2520%253D%25203%253B%250A%250Aconsole.log(a)%253B

...is expecting this case, and is prepared for it, in exactly the way that you expected it would.

I still suspect (having not experimented with it) that the "return this" aliasing will still confuse matters, at least without deliberate code in closure to handle it. At least with strings, it isn't smart enough:
https://closure-compiler.appspot.com/home#code%3D%252F%252F%2520%253D%253DClosureCompiler%253D%253D%250A%252F%252F%2520%2540compilation_level%2520ADVANCED_OPTIMIZATIONS%250A%252F%252F%2520%2540output_file_name%2520default.js%250A%252F%252F%2520%253D%253D%252FClosureCompiler%253D%253D%250A%250A%252F%252F%2520ADD%2520YOUR%2520CODE%2520HERE%250Afunction%2520A()%257Bthis.str%253D%27%27%257D%253B%250AA.prototype.b%2520%253D%2520function(string)%2520%257Bthis.str%252B%253Dstring%253B%2520return%2520this%253B%257D%253B%250AA.prototype.c%2520%253D%2520function()%2520%257Breturn%2520this.str%257D%253B%250A%250Aconsole.log(new%2520A().b(%2522a%2522).b(window.prompt()).b(%2522b%2522).c())%253B
I'm having a hard time modeling this in plain JS to get it to treat the constructor like an object, so as to correctly handle the "return this". Might be better as a @JsOverlay...

---

I played with a similar optimization in GWT a few years ago, but ended up not finishing it. It mostly dealt with trying to deal with compile-time strings and avoiding runtime regular expressions on them, but I abandoned it. If there is enough interest in this, I think I at least have the working knowledge to give it a shot... but lets be sure that we get something out of it.

create({a:'asdf',b:1,c:false});
create(c(b(a({},'asdf'),1),false);
Literal is 31 bytes, chained is 35, so two bytes per setter (plus the cost of the setter method, which can then be reused). Might not be worth more than a few hours of time, but then again, the work could lead to other unexpected benefits...

Peter Donald

unread,
Oct 17, 2017, 11:24:22 PM10/17/17
to GWT Mailing List
On Wed, Oct 18, 2017 at 1:14 PM, Colin Alworth <nilo...@gmail.com> wrote:
For the first one, chaining methods has the unfortunate detail that each method has a return value, which the next object is called on. To my knowledge, there is no optimization in the compiler which currently can undo the presence of the return value, and recognize that the method returns "this" (or a param, some other easily aliased value) as a special case.

Ok. I think I have been spoilt by working on the JVM for so long. I just assumed that GWT and/or Closure could do escape analysis and inline non-escaping objects into the current scope. I assume this is not the case then? It would explain the resulting code.

Lets assume that "str" was only one char, since we are compiling after all, 40 chars for the "idiomatic" js version, 37 chars for staticified version, 50 for the "inlined" version.

Hmm ... I guess I was too down in the trenches to think high level enough. I like this idea. In my context the following is the lay of the land

Current Java Input: 
new InputBuilder().type( "checkbox" ).className( "foo" ).child( "Hello" ).build()

Naive Javascript output (71 chars)
React.createElement('input',{type:'checkbox',className:'foo'},'Hello')

Current GWT output: (lots)
function up(a){var b,c;c=a.b;b=a.a;return $wnd.React.createElement.apply(null,[hw,c].concat(b))}
function vp(a,b){a.a.concat(b);return a}
function wp(a){a.b['className']='foo';return a}
function xp(a){a.b['type']='checkbox';return a}
a=up(vp(wp(xp(new yp)),'Hello'))

Non-reusable GWT output: (82 chars)
function wp(a){a.b['className']='foo';return a}
a=up(vp(wp(xp(new yp)),'Hello'))

Hypothetical Less Naive ES3 Javascript output (45 chars)
ab(dc,{'type':xs,'className':'foo'},'Hello')

Hypothetical Less Naive ES6 Javascript output (36 chars)
ab(dc,{[eg]:xs,[gg]:'foo'},'Hello')

So even if you strip the potentially reusable code out of the current GWT output the non reusable chunk is still 82 characters which is 9 characters more than the most naive javascript implementation. However a more optimized javascript implementation that aliases the methods and constants comes in at 36 chars. However I think that uses some ES6 features ([gg] as key). 

I still suspect (having not experimented with it) that the "return this" aliasing will still confuse matters, at least without deliberate code in closure to handle it. At least with strings, it isn't smart enough:

Right.
 
I played with a similar optimization in GWT a few years ago, but ended up not finishing it. It mostly dealt with trying to deal with compile-time strings and avoiding runtime regular expressions on them, but I abandoned it. If there is enough interest in this, I think I at least have the working knowledge to give it a shot... but lets be sure that we get something out of it.

To be perfectly honest I have no idea on the state of either the GWT or closure optimizer so could not rightly say but I suspect that without an escape analysis this may be a very difficult optimization to get right and probably a lot of work ;) I remember spending weeks  (months?) trying to get some similar optimizations working in a past life and I mostly failed ! But if you want to try don't let me stop you.

create({a:'asdf',b:1,c:false});
create(c(b(a({},'asdf'),1),false);
Literal is 31 bytes, chained is 35, so two bytes per setter (plus the cost of the setter method, which can then be reused). Might not be worth more than a few hours of time, but then again, the work could lead to other unexpected benefits...


I think the optimization would offer a fairly significant improvement but I also think it is a massive amount of work. Maybe time would be better spent elsewhere.

Anyhoo interesting idea - thanks for your reply. 

--
Cheers,

Peter Donald

Peter Donald

unread,
Oct 17, 2017, 11:28:36 PM10/17/17
to GWT Mailing List
I should note I tried lots of different ways to try and get this working and came up with nothing. If you do figure it out - I would love to see what it looks like ;) 

--
Cheers,

Peter Donald
Reply all
Reply to author
Forward
0 new messages