nitj: implement static frame to handle calls
[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 # Calls handling
321
322 # The current `JavaStaticFrame`
323 var frame: nullable JavaStaticFrame = null is writable
324
325 # Code generation
326
327 # Add a line (will be suffixed by `\n`)
328 fun add(line: String) do file.lines.add("{line}\n")
329
330 # Add a new partial line (no `\n` suffix)
331 fun addn(line: String) do file.lines.add(line)
332
333 # Compile a statement (if any)
334 fun stmt(nexpr: nullable AExpr) do
335 if nexpr == null then return
336 nexpr.stmt(self)
337 end
338
339 # Compile an expression an return its result
340 # `mtype` is the expected return type, pass null if no specific type is expected.
341 fun expr(nexpr: AExpr, mtype: nullable MType): RuntimeVariable do
342 var res = null
343 if nexpr.mtype != null then
344 res = nexpr.expr(self)
345 end
346 assert res != null
347 return res
348 end
349
350 # Correctly assign a left and a right value
351 # Boxing and unboxing is performed if required
352 fun assign(left, right: RuntimeVariable) do
353 # TODO right = autobox(right, left.mtype)
354 add("{left} = {right};")
355 end
356
357 # Return a new local RuntimeVariable initialized with the Java expression `jexpr`.
358 #
359 # `mtype` is used for the Java return variable initialization.
360 fun new_expr(jexpr: String, mtype: MType): RuntimeVariable do
361 var res = new_var(mtype)
362 add("{res} = {jexpr};")
363 return res
364 end
365
366 # Display a info message
367 fun info(str: String) do compiler.modelbuilder.toolcontext.info(str, 0)
368 end
369
370 # A file containing Java code.
371 class JavaCodeFile
372
373 # File name
374 var filename: String
375
376 # Lines to write
377 var lines: List[String] = new List[String]
378 end
379
380 redef class MEntity
381 # A Java compatible name for `self`
382 private fun jname: String do return name.to_cmangle
383 end
384
385 # Handler for runtime classes generation
386 #
387 # We need 3 kinds of runtime structures:
388 # * `RTClass` to represent a global class
389 # * `RTMethod` to represent a method definition
390 # * `RTVal` to represent runtime variables
391 class JavaRuntimeModel
392
393 # Compile JavaRuntimeModel structures
394 fun compile_rtmodel(compiler: JavaCompiler) do
395 compile_rtclass(compiler)
396 compile_rtmethod(compiler)
397 compile_rtval(compiler)
398 end
399
400 # Compile the abstract runtime class structure
401 #
402 # Runtime classes have 3 attributes:
403 # * `class_name`: the class name as a String
404 # * `vft`: the virtual function table for the class (flattened)
405 # * `supers`: the super type table (used for type tests)
406 fun compile_rtclass(compiler: JavaCompiler) do
407 var v = compiler.new_visitor("RTClass.java")
408 v.add("import java.util.HashMap;")
409 v.add("public abstract class RTClass \{")
410 v.add(" public String class_name;")
411 v.add(" public HashMap<String, RTMethod> vft = new HashMap<>();")
412 v.add(" public HashMap<String, RTClass> supers = new HashMap<>();")
413 v.add(" protected RTClass() \{\}")
414 v.add("\}")
415 end
416
417 # Compile the abstract runtime method structure
418 #
419 # Method body is executed through the `exec` method:
420 # * `exec` always take an array of RTVal as arg, the first one must be the receiver
421 # * `exec` always returns a RTVal (or null if the Nit return type is void)
422 fun compile_rtmethod(compiler: JavaCompiler) do
423 var v = compiler.new_visitor("RTMethod.java")
424 v.add("public abstract class RTMethod \{")
425 v.add(" protected RTMethod() \{\}")
426 v.add(" public abstract RTVal exec(RTVal[] args);")
427 v.add("\}")
428 end
429
430 # Compile the runtime value structure
431 #
432 # RTVal both represents object instances and primitives values:
433 # * object instances:
434 # * `rtclass` the class of the RTVal is instance of
435 # * `attrs` contains the attributes of the instance
436 # * primitive values:
437 # * `rtclass` represents the class of the primitive value Nit type
438 # * `value` contains the primitive value of the instance
439 # * null values:
440 # * they must have both `rtclass` and `value` as null
441 fun compile_rtval(compiler: JavaCompiler) do
442 var v = compiler.new_visitor("RTVal.java")
443 v.add("import java.util.HashMap;")
444 v.add("public class RTVal \{")
445 v.add(" public RTClass rtclass;")
446 v.add(" public HashMap<String, RTVal> attrs = new HashMap<>();")
447 v.add(" Object value;")
448 v.add(" public RTVal(RTClass rtclass) \{")
449 v.add(" this.rtclass = rtclass;")
450 v.add(" \}")
451 v.add(" public RTVal(RTClass rtclass, Object value) \{")
452 v.add(" this.rtclass = rtclass;")
453 v.add(" this.value = value;")
454 v.add(" \}")
455 v.add(" public boolean is_null() \{ return rtclass == null && value == null; \}")
456 v.add("\}")
457 end
458 end
459
460 # A runtime variable hold a runtime value in Java.
461 # Runtime variables are associated to Nit local variables and intermediate results in Nit expressions.
462 class RuntimeVariable
463
464 # The name of the variable in the Java code
465 var name: String
466
467 # The static type of the variable (as declard in Java)
468 var mtype: MType
469
470 # The current casted type of the variable (as known in Nit)
471 var mcasttype: MType is writable
472
473 # If the variable exaclty a mcasttype?
474 # false (usual value) means that the variable is a mcasttype or a subtype.
475 var is_exact: Bool = false is writable
476
477 # Is this variable declared as a RTVal or a Java primitive one?
478 var is_boxed = false
479
480 redef fun to_s do return name
481
482 redef fun inspect
483 do
484 var exact_str
485 if self.is_exact then
486 exact_str = " exact"
487 else
488 exact_str = ""
489 end
490 var type_str
491 if self.mtype == self.mcasttype then
492 type_str = "{mtype}{exact_str}"
493 else
494 type_str = "{mtype}({mcasttype}{exact_str})"
495 end
496 return "<{name}:{type_str}>"
497 end
498 end
499
500 # The static context of a visited property in a `JavaCompilerVisitor`
501 class JavaStaticFrame
502 # The associated visitor
503 var visitor: JavaCompilerVisitor
504
505 # The executed property.
506 # A Method in case of a call, an attribute in case of a default initialization.
507 var mpropdef: MPropDef
508
509 # The static type of the receiver
510 var receiver: MClassType
511
512 # Arguments of the method (the first is the receiver)
513 var arguments: Array[RuntimeVariable]
514
515 # The runtime_variable associated to the return (in a function)
516 var returnvar: nullable RuntimeVariable = null is writable
517
518 # The label at the end of the property
519 var returnlabel: nullable String = null is writable
520 end
521
522 redef class MType
523 # Return the Java type associated to a given Nit static type
524 fun java_type: String do return "RTVal"
525
526 # Is the associated Java type a primitive one?
527 #
528 # ENSURE `result == (java_type != "Object")`
529 var is_java_primitive: Bool is lazy do return java_type != "RTVal"
530 end
531
532 redef class MClassType
533
534 redef var java_type is lazy do
535 if mclass.name == "Int" then
536 return "int"
537 else if mclass.name == "Bool" then
538 return "boolean"
539 else if mclass.name == "Char" then
540 return "char"
541 else if mclass.name == "Float" then
542 return "double"
543 else if mclass.name == "Byte" then
544 return "byte"
545 else if mclass.name == "NativeString" then
546 return "String"
547 else if mclass.name == "NativeArray" then
548 return "Array"
549 end
550 return "RTVal"
551 end
552 end
553
554 redef class MClass
555
556 # Runtime name
557 private fun rt_name: String do return "RTClass_{intro.mmodule.jname}_{jname}"
558
559 # Generate a Java RTClass for a Nit MClass
560 fun compile_to_java(v: JavaCompilerVisitor) do
561 v.add("public class {rt_name} extends RTClass \{")
562 v.add(" protected static RTClass instance;")
563 v.add(" private {rt_name}() \{")
564 v.add(" this.class_name = \"{name}\";")
565 compile_vft(v)
566 compile_type_table(v)
567 v.add(" \}")
568 v.add(" public static RTClass get{rt_name}() \{")
569 v.add(" if(instance == null) \{")
570 v.add(" instance = new {rt_name}();")
571 v.add(" \}")
572 v.add(" return instance;")
573 v.add(" \}")
574 v.add("\}")
575 end
576
577 # Compile the virtual function table for the mclass
578 private fun compile_vft(v: JavaCompilerVisitor) do
579 # TODO handle generics
580 if mclass_type.need_anchor then return
581 var mclassdefs = mclass_type.collect_mclassdefs(v.compiler.mainmodule).to_a
582 v.compiler.mainmodule.linearize_mclassdefs(mclassdefs)
583
584 var mainmodule = v.compiler.mainmodule
585 for mclassdef in mclassdefs.reversed do
586 for mprop in mclassdef.intro_mproperties do
587 var mpropdef = mprop.lookup_first_definition(mainmodule, intro.bound_mtype)
588 if not mpropdef isa MMethodDef then continue
589 var rt_name = mpropdef.rt_name
590 v.add("this.vft.put(\"{mprop.full_name}\", {rt_name}.get{rt_name}());")
591
592 # fill super next definitions
593 while mpropdef.has_supercall do
594 var prefix = mpropdef.full_name
595 mpropdef = mpropdef.lookup_next_definition(mainmodule, intro.bound_mtype)
596 rt_name = mpropdef.rt_name
597 v.add("this.vft.put(\"{prefix}\", {rt_name}.get{rt_name}());")
598 end
599 end
600 end
601 end
602
603 # Compile the type table for the MClass
604 fun compile_type_table(v: JavaCompilerVisitor) do
605 for pclass in in_hierarchy(v.compiler.mainmodule).greaters do
606 if pclass == self then
607 v.add("supers.put(\"{pclass.jname}\", this);")
608 else
609 v.add("supers.put(\"{pclass.jname}\", {pclass.rt_name}.get{pclass.rt_name}());")
610 end
611 end
612 end
613 end
614
615 redef class MMethodDef
616
617 # Runtime name
618 private fun rt_name: String do
619 return "RTMethod_{mclassdef.mmodule.jname}_{mclassdef.mclass.jname}_{mproperty.jname}"
620 end
621
622 # Generate a Java RTMethod for `self`
623 fun compile_to_java(v: JavaCompilerVisitor) do
624 v.add("public class {rt_name} extends RTMethod \{")
625 v.add(" protected static RTMethod instance;")
626 v.add(" public static RTMethod get{rt_name}() \{")
627 v.add(" if(instance == null) \{")
628 v.add(" instance = new {rt_name}();")
629 v.add(" \}")
630 v.add(" return instance;")
631 v.add(" \}")
632 v.add(" @Override")
633 v.add(" public RTVal exec(RTVal[] args) \{")
634 compile_inside_to_java(v)
635 v.add(" \}")
636 v.add("\}")
637 end
638
639 # Compile the body of this function
640 fun compile_inside_to_java(v: JavaCompilerVisitor) do
641
642 var modelbuilder = v.compiler.modelbuilder
643 var node = modelbuilder.mpropdef2node(self)
644
645 if is_abstract then
646 # TODO compile abstract
647 v.info("NOT YET IMPLEMENTED call to abstract method")
648 v.add("return null;")
649 return
650 end
651
652 if node isa APropdef then
653 node.compile_to_java(v, self)
654 else if node isa AClassdef then
655 # TODO compile attributes
656 v.info("NOT YET IMPLEMENTED attribute handling")
657 v.add("return null;")
658 else
659 abort
660 end
661 end
662 end
663
664 redef class APropdef
665
666 # Compile that property definition to java code
667 fun compile_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef) do
668 v.info("NOT YET IMPLEMENTED {class_name}::compile_to_java")
669 end
670 end
671
672 redef class AMethPropdef
673 redef fun compile_to_java(v, mpropdef) do
674 # TODO Call the implicit super-init
675
676 # Compile intern methods
677 if mpropdef.is_intern then
678 v.info("NOT YET IMPLEMENTED {class_name}::compile_intern")
679 # TODO if compile_intern_to_java(v, mpropdef, arguments) then return
680 v.add("return null;")
681 return
682 end
683
684 # Compile block if any
685 var n_block = n_block
686 if n_block != null then
687 v.stmt(n_block)
688 return
689 end
690 end
691 end
692
693 redef class AExpr
694 # Try to compile self as an expression
695 # Do not call this method directly, use `v.expr` instead
696 private fun expr(v: JavaCompilerVisitor): nullable RuntimeVariable do
697 v.info("NOT YET IMPLEMENTED {class_name}::expr")
698 return null
699 end
700
701 # Try to compile self as a statement
702 # Do not call this method directly, use `v.stmt` instead
703 private fun stmt(v: JavaCompilerVisitor) do expr(v)
704 end
705
706 redef class ABlockExpr
707 redef fun stmt(v)
708 do
709 for e in self.n_expr do v.stmt(e)
710 end
711 redef fun expr(v)
712 do
713 var last = self.n_expr.last
714 for e in self.n_expr do
715 if e == last then break
716 v.stmt(e)
717 end
718 return v.expr(last, null)
719 end
720 end
721
722 redef class ADebugTypeExpr
723 redef fun stmt(v) do end # do nothing
724 end