nitj: build naive type tables using Java HashMaps
[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 # Code generation
265
266 # Add a line (will be suffixed by `\n`)
267 fun add(line: String) do file.lines.add("{line}\n")
268
269 # Add a new partial line (no `\n` suffix)
270 fun addn(line: String) do file.lines.add(line)
271 end
272
273 # A file containing Java code.
274 class JavaCodeFile
275
276 # File name
277 var filename: String
278
279 # Lines to write
280 var lines: List[String] = new List[String]
281 end
282
283 redef class MEntity
284 # A Java compatible name for `self`
285 private fun jname: String do return name.to_cmangle
286 end
287
288 # Handler for runtime classes generation
289 #
290 # We need 3 kinds of runtime structures:
291 # * `RTClass` to represent a global class
292 # * `RTMethod` to represent a method definition
293 # * `RTVal` to represent runtime variables
294 class JavaRuntimeModel
295
296 # Compile JavaRuntimeModel structures
297 fun compile_rtmodel(compiler: JavaCompiler) do
298 compile_rtclass(compiler)
299 compile_rtmethod(compiler)
300 compile_rtval(compiler)
301 end
302
303 # Compile the abstract runtime class structure
304 #
305 # Runtime classes have 3 attributes:
306 # * `class_name`: the class name as a String
307 # * `vft`: the virtual function table for the class (flattened)
308 # * `supers`: the super type table (used for type tests)
309 fun compile_rtclass(compiler: JavaCompiler) do
310 var v = compiler.new_visitor("RTClass.java")
311 v.add("import java.util.HashMap;")
312 v.add("public abstract class RTClass \{")
313 v.add(" public String class_name;")
314 v.add(" public HashMap<String, RTMethod> vft = new HashMap<>();")
315 v.add(" public HashMap<String, RTClass> supers = new HashMap<>();")
316 v.add(" protected RTClass() \{\}")
317 v.add("\}")
318 end
319
320 # Compile the abstract runtime method structure
321 #
322 # Method body is executed through the `exec` method:
323 # * `exec` always take an array of RTVal as arg, the first one must be the receiver
324 # * `exec` always returns a RTVal (or null if the Nit return type is void)
325 fun compile_rtmethod(compiler: JavaCompiler) do
326 var v = compiler.new_visitor("RTMethod.java")
327 v.add("public abstract class RTMethod \{")
328 v.add(" protected RTMethod() \{\}")
329 v.add(" public abstract RTVal exec(RTVal[] args);")
330 v.add("\}")
331 end
332
333 # Compile the runtime value structure
334 #
335 # RTVal both represents object instances and primitives values:
336 # * object instances:
337 # * `rtclass` the class of the RTVal is instance of
338 # * `attrs` contains the attributes of the instance
339 # * primitive values:
340 # * `rtclass` represents the class of the primitive value Nit type
341 # * `value` contains the primitive value of the instance
342 # * null values:
343 # * they must have both `rtclass` and `value` as null
344 fun compile_rtval(compiler: JavaCompiler) do
345 var v = compiler.new_visitor("RTVal.java")
346 v.add("import java.util.HashMap;")
347 v.add("public class RTVal \{")
348 v.add(" public RTClass rtclass;")
349 v.add(" public HashMap<String, RTVal> attrs = new HashMap<>();")
350 v.add(" Object value;")
351 v.add(" public RTVal(RTClass rtclass) \{")
352 v.add(" this.rtclass = rtclass;")
353 v.add(" \}")
354 v.add(" public RTVal(RTClass rtclass, Object value) \{")
355 v.add(" this.rtclass = rtclass;")
356 v.add(" this.value = value;")
357 v.add(" \}")
358 v.add(" public boolean is_null() \{ return rtclass == null && value == null; \}")
359 v.add("\}")
360 end
361 end
362
363 redef class MClass
364
365 # Runtime name
366 private fun rt_name: String do return "RTClass_{intro.mmodule.jname}_{jname}"
367
368 # Generate a Java RTClass for a Nit MClass
369 fun compile_to_java(v: JavaCompilerVisitor) do
370 v.add("public class {rt_name} extends RTClass \{")
371 v.add(" protected static RTClass instance;")
372 v.add(" private {rt_name}() \{")
373 v.add(" this.class_name = \"{name}\";")
374 compile_vft(v)
375 compile_type_table(v)
376 v.add(" \}")
377 v.add(" public static RTClass get{rt_name}() \{")
378 v.add(" if(instance == null) \{")
379 v.add(" instance = new {rt_name}();")
380 v.add(" \}")
381 v.add(" return instance;")
382 v.add(" \}")
383 v.add("\}")
384 end
385
386 # Compile the virtual function table for the mclass
387 private fun compile_vft(v: JavaCompilerVisitor) do
388 # TODO handle generics
389 if mclass_type.need_anchor then return
390 var mclassdefs = mclass_type.collect_mclassdefs(v.compiler.mainmodule).to_a
391 v.compiler.mainmodule.linearize_mclassdefs(mclassdefs)
392
393 var mainmodule = v.compiler.mainmodule
394 for mclassdef in mclassdefs.reversed do
395 for mprop in mclassdef.intro_mproperties do
396 var mpropdef = mprop.lookup_first_definition(mainmodule, intro.bound_mtype)
397 if not mpropdef isa MMethodDef then continue
398 var rt_name = mpropdef.rt_name
399 v.add("this.vft.put(\"{mprop.full_name}\", {rt_name}.get{rt_name}());")
400
401 # fill super next definitions
402 while mpropdef.has_supercall do
403 var prefix = mpropdef.full_name
404 mpropdef = mpropdef.lookup_next_definition(mainmodule, intro.bound_mtype)
405 rt_name = mpropdef.rt_name
406 v.add("this.vft.put(\"{prefix}\", {rt_name}.get{rt_name}());")
407 end
408 end
409 end
410 end
411
412 # Compile the type table for the MClass
413 fun compile_type_table(v: JavaCompilerVisitor) do
414 for pclass in in_hierarchy(v.compiler.mainmodule).greaters do
415 if pclass == self then
416 v.add("supers.put(\"{pclass.jname}\", this);")
417 else
418 v.add("supers.put(\"{pclass.jname}\", {pclass.rt_name}.get{pclass.rt_name}());")
419 end
420 end
421 end
422 end
423
424 redef class MMethodDef
425
426 # Runtime name
427 private fun rt_name: String do
428 return "RTMethod_{mclassdef.mmodule.jname}_{mclassdef.mclass.jname}_{mproperty.jname}"
429 end
430
431 # Generate a Java RTMethod for `self`
432 fun compile_to_java(v: JavaCompilerVisitor) do
433 v.add("public class {rt_name} extends RTMethod \{")
434 v.add(" protected static RTMethod instance;")
435 v.add(" public static RTMethod get{rt_name}() \{")
436 v.add(" if(instance == null) \{")
437 v.add(" instance = new {rt_name}();")
438 v.add(" \}")
439 v.add(" return instance;")
440 v.add(" \}")
441 v.add(" @Override")
442 v.add(" public RTVal exec(RTVal[] args) \{")
443 # TODO compile_inside_to_java(v)
444 v.add(" return null;")
445 v.add(" \}")
446 v.add("\}")
447 end
448 end