nitj: prepare node visit of method bodies
[nit.git] / src / compiler / java_compiler.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Compile Nit code to Java code
16 #
17 # 3 runtime structures are used to represent Nit instance in Java generated code:
18 # * `RTClass` to represent a class, it's super-type table and its VFT
19 # * `RTMethod` to reprensent a compiled method definition
20 # * `RTVal` to reprensent a Nit instance, the null value or a native value
21 #
22 # More details are given in the documentation of these 3 classes.
23 #
24 # TODO Factorize with `abstract_compiler`
25 module java_compiler
26
27 import rapid_type_analysis
28 import frontend
29
30 redef class ToolContext
31
32 # Where to output the generated binary
33 var opt_output = new OptionString("Output file", "-o", "--output")
34
35 # Where to output tmp files
36 var opt_compile_dir = new OptionString("Directory used to generate temporary files", "--compile-dir")
37
38 redef init do
39 super
40 option_context.add_option(opt_output, opt_compile_dir)
41 end
42 end
43
44 redef class ModelBuilder
45
46 # Start the Java compiler
47 fun run_java_compiler(mainmodule: MModule, runtime_type_analysis: RapidTypeAnalysis) do
48 var time0 = get_time
49 toolcontext.info("*** GENERATING JAVA ***", 1)
50
51 var compiler = new JavaCompiler(mainmodule, self, runtime_type_analysis)
52 compiler.do_compilation
53
54 var time1 = get_time
55 toolcontext.info("*** END GENERATING JAVA: {time1-time0} ***", 2)
56 write_and_make(compiler)
57 end
58
59 # Write Java code and compile it into an executable jar
60 fun write_and_make(compiler: JavaCompiler) do
61 var time0 = get_time
62 toolcontext.info("*** WRITING JAVA ***", 1)
63
64 compiler.compile_dir.mkdir
65
66 var jfiles = write_java_files(compiler)
67
68 var time1 = get_time
69 toolcontext.info("*** END WRITING JAVA: {time1-time0} ***", 2)
70
71 time0 = time1
72 toolcontext.info("*** COMPILING JAVA ***", 1)
73
74 build_with_make(compiler, jfiles)
75 write_shell_script(compiler)
76
77 time1 = get_time
78 toolcontext.info("*** END COMPILING JAVA: {time1-time0} ***", 2)
79 end
80
81 # Write files managed by `compiler` into concrete files
82 fun write_java_files(compiler: JavaCompiler): Array[String] do
83 var jfiles = new Array[String]
84 for f in compiler.files do
85 var file = new FileWriter.open("{compiler.compile_dir}/{f.filename}")
86 for line in f.lines do file.write(line)
87 file.close
88 jfiles.add(f.filename)
89 end
90 return jfiles
91 end
92
93 # Compile Java generated files using `make`
94 fun build_with_make(compiler: JavaCompiler, jfiles: Array[String]) do
95 write_manifest(compiler)
96 write_makefile(compiler, jfiles)
97 var compile_dir = compiler.compile_dir
98 var outname = compiler.outname.to_path.filename
99 toolcontext.info("make -N -C {compile_dir} -f {outname}.mk", 2)
100 var res
101 if toolcontext.verbose_level >= 3 then
102 res = sys.system("make -B -C {compile_dir} -f {outname}.mk 2>&1")
103 else
104 res = sys.system("make -B -C {compile_dir} -f {outname}.mk 2>&1 > /dev/null")
105 end
106 if res != 0 then toolcontext.error(null, "make failed! Error code: {res}.")
107 end
108
109 # Write the Makefile used to compile Java generated files into an executable jar
110 fun write_makefile(compiler: JavaCompiler, jfiles: Array[String]) do
111 # list class files from jfiles
112 var ofiles = new List[String]
113 for f in jfiles do ofiles.add(f.strip_extension(".java") + ".class")
114
115 var compile_dir = compiler.compile_dir
116 var outname = compiler.outname.to_path.filename
117 var outpath = (sys.getcwd / compiler.outname).simplify_path
118 var makename = "{compile_dir}/{outname}.mk"
119 var makefile = new FileWriter.open(makename)
120
121 makefile.write("JC = javac\n")
122 makefile.write("JAR = jar\n\n")
123
124 makefile.write("all: {outpath}.jar\n\n")
125
126 makefile.write("{outpath}.jar: {compiler.mainmodule.jname}_Main.class\n")
127 makefile.write("\t$(JAR) cfm {outpath}.jar {outname}.mf {ofiles.join(" ")}\n\n")
128
129 makefile.write("{compiler.mainmodule.jname}_Main.class:\n")
130 makefile.write("\t$(JC) {jfiles.join(" ")}\n\n")
131
132 makefile.write("clean:\n")
133 makefile.write("\trm {ofiles.join(" ")} 2>/dev/null\n\n")
134
135 makefile.close
136 toolcontext.info("Generated makefile: {makename}", 2)
137 end
138
139 # Write the Java manifest file
140 private fun write_manifest(compiler: JavaCompiler) do
141 var compile_dir = compiler.compile_dir
142 var outname = compiler.outname.to_path.filename
143 var maniffile = new FileWriter.open("{compile_dir}/{outname}.mf")
144 maniffile.write("Manifest-Version: 1.0\n")
145 maniffile.write("Main-Class: {compiler.mainmodule.jname}_Main\n")
146 maniffile.close
147 end
148
149 # Write a simple bash script that runs the jar like it was a binary generated by nitc
150 private fun write_shell_script(compiler: JavaCompiler) do
151 var outname = compiler.outname
152 var shfile = new FileWriter.open(outname)
153 shfile.write("#!/bin/bash\n")
154 shfile.write("java -jar {outname}.jar \"$@\"\n")
155 shfile.close
156 sys.system("chmod +x {outname}")
157 end
158 end
159
160 # Compiler that translates Nit code to Java code
161 class JavaCompiler
162 # The main module of the program currently compiled
163 var mainmodule: MModule
164
165 # Modelbuilder used to know the model and the AST
166 var modelbuilder: ModelBuilder
167
168 # The result of the RTA (used to know live types and methods)
169 var runtime_type_analysis: RapidTypeAnalysis
170
171 # Where to generate tmp files
172 var compile_dir: String is lazy do
173 var dir = modelbuilder.toolcontext.opt_compile_dir.value
174 if dir == null then dir = "nitj_compile"
175 return dir
176 end
177
178 # Name of the generated executable
179 var outname: String is lazy do
180 var name = modelbuilder.toolcontext.opt_output.value
181 if name == null then name = mainmodule.jname
182 return name
183 end
184
185 # The list of all associated files
186 # Used to generate .java files
187 var files: Array[JavaCodeFile] = new Array[JavaCodeFile]
188
189 # Force the creation of a new file
190 # The point is to avoid contamination between must-be-compiled-separately files
191 fun new_file(name: String): JavaCodeFile do
192 var file = new JavaCodeFile(name)
193 files.add(file)
194 return file
195 end
196
197 # Kind of visitor to use
198 type VISITOR: JavaCompilerVisitor
199
200 # Initialize a visitor specific for the compiler engine
201 fun new_visitor(filename: String): VISITOR do
202 return new JavaCompilerVisitor(self, new_file(filename))
203 end
204
205 # RuntimeModel representation
206 private var rt_model: JavaRuntimeModel is lazy do return new JavaRuntimeModel
207
208 # Compile Nit code to Java
209 fun do_compilation do
210 # compile java classes used to represents the runtime model of the program
211 rt_model.compile_rtmodel(self)
212
213 # compile class structures
214 compile_mclasses_to_java
215
216 # compile method structures
217 compile_mmethods_to_java
218
219 # TODO compile main
220 modelbuilder.toolcontext.info("NOT YET IMPLEMENTED", 0)
221 end
222
223 # Generate a `RTClass` for each `MClass` found in model
224 #
225 # This is a global phase because we need to know all the program to build
226 # attributes, fill vft and type table.
227 fun compile_mclasses_to_java do
228 for mclass in mainmodule.model.mclasses do
229 mclass.compile_to_java(new_visitor("{mclass.rt_name}.java"))
230 end
231 end
232
233 # Generate a `RTMethod` for each `MMethodDef` found in model
234 #
235 # This is a separate phase.
236 fun compile_mmethods_to_java do
237 for mmodule in mainmodule.in_importation.greaters do
238 for mclassdef in mmodule.mclassdefs do
239 for mdef in mclassdef.mpropdefs do
240 if mdef isa MMethodDef then
241 mdef.compile_to_java(new_visitor("{mdef.rt_name}.java"))
242 end
243 end
244 end
245 end
246 end
247 end
248
249 # The class visiting the AST
250 #
251 # A visitor is attached to one JavaCodeFile it writes into.
252 class JavaCompilerVisitor
253 super Visitor
254
255 # JavaCompiler used with this visitor
256 type COMPILER: JavaCompiler
257
258 # The associated compiler
259 var compiler: JavaCompiler
260
261 # The file to write generated code into
262 var file: JavaCodeFile
263
264 # Names handling
265
266 private var names = new HashSet[String]
267 private var last: Int = 0
268
269 # Return a new name based on `s` and unique in the visitor
270 fun get_name(s: String): String do
271 if not self.names.has(s) then
272 self.names.add(s)
273 return s
274 end
275 var i = self.last + 1
276 loop
277 var s2 = s + i.to_s
278 if not self.names.has(s2) then
279 self.last = i
280 self.names.add(s2)
281 return s2
282 end
283 i = i + 1
284 end
285 end
286
287 # Variables handling
288
289 # Registered variables
290 protected var variables = new HashMap[Variable, RuntimeVariable]
291
292 # Return the local RuntimeVariable associated to a Nit local variable
293 fun variable(variable: Variable): RuntimeVariable do
294 if variables.has_key(variable) then
295 return variables[variable]
296 else
297 var name = get_name("var_{variable.name}")
298 var mtype = variable.declared_type.as(not null)
299 # TODO mtype = self.anchor(mtype)
300 var res = decl_var(name, mtype)
301 variables[variable] = res
302 return res
303 end
304 end
305
306 # Return a new uninitialized local RuntimeVariable with `name`
307 fun decl_var(name: String, mtype: MType): RuntimeVariable do
308 var res = new RuntimeVariable(name, mtype, mtype)
309 add("{mtype.java_type} {name} /* : {mtype} */;")
310 return res
311 end
312
313 # Return a new uninitialized local RuntimeVariable
314 fun new_var(mtype: MType): RuntimeVariable do
315 # TODO mtype = self.anchor(mtype)
316 var name = self.get_name("var")
317 return decl_var(name, mtype)
318 end
319
320 # Code generation
321
322 # Add a line (will be suffixed by `\n`)
323 fun add(line: String) do file.lines.add("{line}\n")
324
325 # Add a new partial line (no `\n` suffix)
326 fun addn(line: String) do file.lines.add(line)
327
328 # Compile a statement (if any)
329 fun stmt(nexpr: nullable AExpr) do
330 if nexpr == null then return
331 nexpr.stmt(self)
332 end
333
334 # Compile an expression an return its result
335 # `mtype` is the expected return type, pass null if no specific type is expected.
336 fun expr(nexpr: AExpr, mtype: nullable MType): RuntimeVariable do
337 var res = null
338 if nexpr.mtype != null then
339 res = nexpr.expr(self)
340 end
341 assert res != null
342 return res
343 end
344
345 # Correctly assign a left and a right value
346 # Boxing and unboxing is performed if required
347 fun assign(left, right: RuntimeVariable) do
348 # TODO right = autobox(right, left.mtype)
349 add("{left} = {right};")
350 end
351
352 # Return a new local RuntimeVariable initialized with the Java expression `jexpr`.
353 #
354 # `mtype` is used for the Java return variable initialization.
355 fun new_expr(jexpr: String, mtype: MType): RuntimeVariable do
356 var res = new_var(mtype)
357 add("{res} = {jexpr};")
358 return res
359 end
360
361 # Display a info message
362 fun info(str: String) do compiler.modelbuilder.toolcontext.info(str, 0)
363 end
364
365 # A file containing Java code.
366 class JavaCodeFile
367
368 # File name
369 var filename: String
370
371 # Lines to write
372 var lines: List[String] = new List[String]
373 end
374
375 redef class MEntity
376 # A Java compatible name for `self`
377 private fun jname: String do return name.to_cmangle
378 end
379
380 # Handler for runtime classes generation
381 #
382 # We need 3 kinds of runtime structures:
383 # * `RTClass` to represent a global class
384 # * `RTMethod` to represent a method definition
385 # * `RTVal` to represent runtime variables
386 class JavaRuntimeModel
387
388 # Compile JavaRuntimeModel structures
389 fun compile_rtmodel(compiler: JavaCompiler) do
390 compile_rtclass(compiler)
391 compile_rtmethod(compiler)
392 compile_rtval(compiler)
393 end
394
395 # Compile the abstract runtime class structure
396 #
397 # Runtime classes have 3 attributes:
398 # * `class_name`: the class name as a String
399 # * `vft`: the virtual function table for the class (flattened)
400 # * `supers`: the super type table (used for type tests)
401 fun compile_rtclass(compiler: JavaCompiler) do
402 var v = compiler.new_visitor("RTClass.java")
403 v.add("import java.util.HashMap;")
404 v.add("public abstract class RTClass \{")
405 v.add(" public String class_name;")
406 v.add(" public HashMap<String, RTMethod> vft = new HashMap<>();")
407 v.add(" public HashMap<String, RTClass> supers = new HashMap<>();")
408 v.add(" protected RTClass() \{\}")
409 v.add("\}")
410 end
411
412 # Compile the abstract runtime method structure
413 #
414 # Method body is executed through the `exec` method:
415 # * `exec` always take an array of RTVal as arg, the first one must be the receiver
416 # * `exec` always returns a RTVal (or null if the Nit return type is void)
417 fun compile_rtmethod(compiler: JavaCompiler) do
418 var v = compiler.new_visitor("RTMethod.java")
419 v.add("public abstract class RTMethod \{")
420 v.add(" protected RTMethod() \{\}")
421 v.add(" public abstract RTVal exec(RTVal[] args);")
422 v.add("\}")
423 end
424
425 # Compile the runtime value structure
426 #
427 # RTVal both represents object instances and primitives values:
428 # * object instances:
429 # * `rtclass` the class of the RTVal is instance of
430 # * `attrs` contains the attributes of the instance
431 # * primitive values:
432 # * `rtclass` represents the class of the primitive value Nit type
433 # * `value` contains the primitive value of the instance
434 # * null values:
435 # * they must have both `rtclass` and `value` as null
436 fun compile_rtval(compiler: JavaCompiler) do
437 var v = compiler.new_visitor("RTVal.java")
438 v.add("import java.util.HashMap;")
439 v.add("public class RTVal \{")
440 v.add(" public RTClass rtclass;")
441 v.add(" public HashMap<String, RTVal> attrs = new HashMap<>();")
442 v.add(" Object value;")
443 v.add(" public RTVal(RTClass rtclass) \{")
444 v.add(" this.rtclass = rtclass;")
445 v.add(" \}")
446 v.add(" public RTVal(RTClass rtclass, Object value) \{")
447 v.add(" this.rtclass = rtclass;")
448 v.add(" this.value = value;")
449 v.add(" \}")
450 v.add(" public boolean is_null() \{ return rtclass == null && value == null; \}")
451 v.add("\}")
452 end
453 end
454
455 # A runtime variable hold a runtime value in Java.
456 # Runtime variables are associated to Nit local variables and intermediate results in Nit expressions.
457 class RuntimeVariable
458
459 # The name of the variable in the Java code
460 var name: String
461
462 # The static type of the variable (as declard in Java)
463 var mtype: MType
464
465 # The current casted type of the variable (as known in Nit)
466 var mcasttype: MType is writable
467
468 # If the variable exaclty a mcasttype?
469 # false (usual value) means that the variable is a mcasttype or a subtype.
470 var is_exact: Bool = false is writable
471
472 # Is this variable declared as a RTVal or a Java primitive one?
473 var is_boxed = false
474
475 redef fun to_s do return name
476
477 redef fun inspect
478 do
479 var exact_str
480 if self.is_exact then
481 exact_str = " exact"
482 else
483 exact_str = ""
484 end
485 var type_str
486 if self.mtype == self.mcasttype then
487 type_str = "{mtype}{exact_str}"
488 else
489 type_str = "{mtype}({mcasttype}{exact_str})"
490 end
491 return "<{name}:{type_str}>"
492 end
493 end
494
495 redef class MType
496 # Return the Java type associated to a given Nit static type
497 fun java_type: String do return "RTVal"
498
499 # Is the associated Java type a primitive one?
500 #
501 # ENSURE `result == (java_type != "Object")`
502 var is_java_primitive: Bool is lazy do return java_type != "RTVal"
503 end
504
505 redef class MClassType
506
507 redef var java_type is lazy do
508 if mclass.name == "Int" then
509 return "int"
510 else if mclass.name == "Bool" then
511 return "boolean"
512 else if mclass.name == "Char" then
513 return "char"
514 else if mclass.name == "Float" then
515 return "double"
516 else if mclass.name == "Byte" then
517 return "byte"
518 else if mclass.name == "NativeString" then
519 return "String"
520 else if mclass.name == "NativeArray" then
521 return "Array"
522 end
523 return "RTVal"
524 end
525 end
526
527 redef class MClass
528
529 # Runtime name
530 private fun rt_name: String do return "RTClass_{intro.mmodule.jname}_{jname}"
531
532 # Generate a Java RTClass for a Nit MClass
533 fun compile_to_java(v: JavaCompilerVisitor) do
534 v.add("public class {rt_name} extends RTClass \{")
535 v.add(" protected static RTClass instance;")
536 v.add(" private {rt_name}() \{")
537 v.add(" this.class_name = \"{name}\";")
538 compile_vft(v)
539 compile_type_table(v)
540 v.add(" \}")
541 v.add(" public static RTClass get{rt_name}() \{")
542 v.add(" if(instance == null) \{")
543 v.add(" instance = new {rt_name}();")
544 v.add(" \}")
545 v.add(" return instance;")
546 v.add(" \}")
547 v.add("\}")
548 end
549
550 # Compile the virtual function table for the mclass
551 private fun compile_vft(v: JavaCompilerVisitor) do
552 # TODO handle generics
553 if mclass_type.need_anchor then return
554 var mclassdefs = mclass_type.collect_mclassdefs(v.compiler.mainmodule).to_a
555 v.compiler.mainmodule.linearize_mclassdefs(mclassdefs)
556
557 var mainmodule = v.compiler.mainmodule
558 for mclassdef in mclassdefs.reversed do
559 for mprop in mclassdef.intro_mproperties do
560 var mpropdef = mprop.lookup_first_definition(mainmodule, intro.bound_mtype)
561 if not mpropdef isa MMethodDef then continue
562 var rt_name = mpropdef.rt_name
563 v.add("this.vft.put(\"{mprop.full_name}\", {rt_name}.get{rt_name}());")
564
565 # fill super next definitions
566 while mpropdef.has_supercall do
567 var prefix = mpropdef.full_name
568 mpropdef = mpropdef.lookup_next_definition(mainmodule, intro.bound_mtype)
569 rt_name = mpropdef.rt_name
570 v.add("this.vft.put(\"{prefix}\", {rt_name}.get{rt_name}());")
571 end
572 end
573 end
574 end
575
576 # Compile the type table for the MClass
577 fun compile_type_table(v: JavaCompilerVisitor) do
578 for pclass in in_hierarchy(v.compiler.mainmodule).greaters do
579 if pclass == self then
580 v.add("supers.put(\"{pclass.jname}\", this);")
581 else
582 v.add("supers.put(\"{pclass.jname}\", {pclass.rt_name}.get{pclass.rt_name}());")
583 end
584 end
585 end
586 end
587
588 redef class MMethodDef
589
590 # Runtime name
591 private fun rt_name: String do
592 return "RTMethod_{mclassdef.mmodule.jname}_{mclassdef.mclass.jname}_{mproperty.jname}"
593 end
594
595 # Generate a Java RTMethod for `self`
596 fun compile_to_java(v: JavaCompilerVisitor) do
597 v.add("public class {rt_name} extends RTMethod \{")
598 v.add(" protected static RTMethod instance;")
599 v.add(" public static RTMethod get{rt_name}() \{")
600 v.add(" if(instance == null) \{")
601 v.add(" instance = new {rt_name}();")
602 v.add(" \}")
603 v.add(" return instance;")
604 v.add(" \}")
605 v.add(" @Override")
606 v.add(" public RTVal exec(RTVal[] args) \{")
607 compile_inside_to_java(v)
608 v.add(" \}")
609 v.add("\}")
610 end
611
612 # Compile the body of this function
613 fun compile_inside_to_java(v: JavaCompilerVisitor) do
614
615 var modelbuilder = v.compiler.modelbuilder
616 var node = modelbuilder.mpropdef2node(self)
617
618 if is_abstract then
619 # TODO compile abstract
620 v.info("NOT YET IMPLEMENTED call to abstract method")
621 v.add("return null;")
622 return
623 end
624
625 if node isa APropdef then
626 node.compile_to_java(v, self)
627 else if node isa AClassdef then
628 # TODO compile attributes
629 v.info("NOT YET IMPLEMENTED attribute handling")
630 v.add("return null;")
631 else
632 abort
633 end
634 end
635 end
636
637 redef class APropdef
638
639 # Compile that property definition to java code
640 fun compile_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef) do
641 v.info("NOT YET IMPLEMENTED {class_name}::compile_to_java")
642 end
643 end
644
645 redef class AMethPropdef
646 redef fun compile_to_java(v, mpropdef) do
647 # TODO Call the implicit super-init
648
649 # Compile intern methods
650 if mpropdef.is_intern then
651 v.info("NOT YET IMPLEMENTED {class_name}::compile_intern")
652 # TODO if compile_intern_to_java(v, mpropdef, arguments) then return
653 v.add("return null;")
654 return
655 end
656
657 # Compile block if any
658 var n_block = n_block
659 if n_block != null then
660 v.stmt(n_block)
661 return
662 end
663 end
664 end
665
666 redef class AExpr
667 # Try to compile self as an expression
668 # Do not call this method directly, use `v.expr` instead
669 private fun expr(v: JavaCompilerVisitor): nullable RuntimeVariable do
670 v.info("NOT YET IMPLEMENTED {class_name}::expr")
671 return null
672 end
673
674 # Try to compile self as a statement
675 # Do not call this method directly, use `v.stmt` instead
676 private fun stmt(v: JavaCompilerVisitor) do expr(v)
677 end
678
679 redef class ABlockExpr
680 redef fun stmt(v)
681 do
682 for e in self.n_expr do v.stmt(e)
683 end
684 redef fun expr(v)
685 do
686 var last = self.n_expr.last
687 for e in self.n_expr do
688 if e == last then break
689 v.stmt(e)
690 end
691 return v.expr(last, null)
692 end
693 end
694
695 redef class ADebugTypeExpr
696 redef fun stmt(v) do end # do nothing
697 end