# The class visiting the AST
#
# A visitor is attached to one JavaCodeFile it writes into.
class JavaCompilerVisitor
super Visitor
# JavaCompiler used with this visitor
type COMPILER: JavaCompiler
# The associated compiler
var compiler: JavaCompiler
# The file to write generated code into
var file: JavaCodeFile
# Names handling
private var names = new HashSet[String]
private var last: Int = 0
# Return a new name based on `s` and unique in the visitor
fun get_name(s: String): String do
if not self.names.has(s) then
self.names.add(s)
return s
end
var i = self.last + 1
loop
var s2 = s + i.to_s
if not self.names.has(s2) then
self.last = i
self.names.add(s2)
return s2
end
i = i + 1
end
end
# Return an unique and stable identifier associated with an escapemark
fun escapemark_name(e: nullable EscapeMark): String do
assert e != null
var frame = self.frame
assert frame != null
if frame.escapemark_names.has_key(e) then return frame.escapemark_names[e]
var name = e.name
if name == null then name = "label"
name = get_name(name)
frame.escapemark_names[e] = name
return name
end
# Insert a C label for associated with an escapemark
fun add_escape_label(e: nullable EscapeMark) do
if e == null then return
if e.escapes.is_empty then return
add("BREAK_{escapemark_name(e)}: ")
end
# Variables handling
# Registered variables
protected var variables = new HashMap[Variable, RuntimeVariable]
# Return the local RuntimeVariable associated to a Nit local variable
fun variable(variable: Variable): RuntimeVariable do
if variables.has_key(variable) then
return variables[variable]
else
var name = get_name("var_{variable.name}")
var mtype = variable.declared_type.as(not null)
mtype = anchor(mtype)
var res = decl_var(name, mtype)
variables[variable] = res
return res
end
end
# Return a new uninitialized local RuntimeVariable with `name`
fun decl_var(name: String, mtype: MType): RuntimeVariable do
var res = new RuntimeVariable(name, mtype, mtype)
res.is_boxed = not mtype.is_java_primitive
add("{mtype.java_type} {name} /* : {mtype} */;")
return res
end
# Return a new uninitialized local RuntimeVariable
fun new_var(mtype: MType): RuntimeVariable do
mtype = anchor(mtype)
var name = self.get_name("var")
return decl_var(name, mtype)
end
# Calls handling
# The current `JavaStaticFrame`
var frame: nullable JavaStaticFrame = null is writable
# Return a new local RuntimeVariable initialized from `args[0]`
fun new_recv(mtype: MType): RuntimeVariable do
var res = new_var(mtype)
add("{res} = args[0];")
return res
end
# Calls handling
# Compile a call within a callsite
fun compile_callsite(callsite: CallSite, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
var initializers = callsite.mpropdef.initializers
if not initializers.is_empty then
var recv = arguments.first
var i = 1
for p in initializers do
if p isa MMethod then
var args = [recv]
var msignature = p.intro.msignature
if msignature != null then
for x in msignature.mparameters do
args.add arguments[i]
i += 1
end
end
send(p, args)
else if p isa MAttribute then
info("NOT YET IMPLEMENTED {class_name}::compile_callsite for MAttribute `{p}`")
#self.write_attribute(p, recv, arguments[i])
i += 1
else abort
end
assert i == arguments.length
return send(callsite.mproperty, [recv])
end
return send(callsite.mproperty, arguments)
end
# Evaluate `args` as expressions in the call of `mpropdef` on `recv`.
#
# This method is used to manage varargs in signatures and returns the real array
# of runtime variables to use in the call.
fun varargize(mpropdef: MMethodDef, map: nullable SignatureMap, recv: RuntimeVariable, args: SequenceRead[AExpr]): Array[RuntimeVariable] do
var msignature = mpropdef.msignature.as(not null)
var res = new Array[RuntimeVariable]
res.add(recv)
if msignature.arity == 0 then return res
if map == null then
assert args.length == msignature.arity
for ne in args do
res.add expr(ne, null)
end
return res
end
# Eval in order of arguments, not parameters
var exprs = new Array[RuntimeVariable].with_capacity(args.length)
for ne in args do
exprs.add expr(ne, null)
end
# Fill `res` with the result of the evaluation according to the mapping
for i in [0..msignature.arity[ do
var param = msignature.mparameters[i]
var j = map.map.get_or_null(i)
if j == null then
# default value
res.add(null_instance)
continue
end
if param.is_vararg and args[i].vararg_decl > 0 then
var vararg = exprs.sub(j, args[i].vararg_decl)
var elttype = param.mtype
var arg = self.vararg_instance(mpropdef, recv, vararg, elttype)
res.add(arg)
continue
end
res.add exprs[j]
end
return res
end
# Generate a static call on a method definition (no receiver needed).
fun static_call(mmethoddef: MMethodDef, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
var res: nullable RuntimeVariable
var ret = mmethoddef.msignature.as(not null).return_mtype
if ret == null then
res = null
else
ret = ret.resolve_for(mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.mmodule, true)
res = self.new_var(ret)
end
# Autobox arguments
adapt_signature(mmethoddef, arguments)
var rt_name = mmethoddef.rt_name
if res == null then
add("{rt_name}.get{rt_name}().exec(new RTVal[]\{{arguments.join(",")}\});")
return null
end
var ress = new_expr("{rt_name}.get{rt_name}().exec(new RTVal[]\{{arguments.join(",")}\});", compiler.mainmodule.object_type)
assign(res, ress)
return res
end
# Generate a polymorphic send for `method` with `arguments`
fun send(mmethod: MMethod, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
# Shortcut calls on primitives
if arguments.first.mcasttype.is_java_primitive then
return monomorphic_send(mmethod, arguments.first.mcasttype, arguments)
end
# Polymorphic send
return table_send(mmethod, arguments)
end
# Handle common special cases before doing the effective method invocation
# This methods handle the `==` and `!=` methods and the case of the null receiver.
# Note: a { is open in the generated C, that enclose and protect the effective method invocation.
# Client must not forget to close the } after them.
#
# The value returned is the result of the common special cases.
# If not null, client must compile it with the result of their own effective method invocation.
#
# If `before_send` can shortcut the whole message sending, a dummy `if(0){`
# is generated to cancel the effective method invocation that will follow
# TODO: find a better approach
private fun before_send(res: nullable RuntimeVariable, mmethod: MMethodDef, arguments: Array[RuntimeVariable]) do
var bool_type = compiler.mainmodule.bool_type
var recv = arguments.first
var consider_null = mmethod.name == "==" or mmethod.name == "!=" or mmethod.name == "is_same_instance"
if recv.mcasttype isa MNullableType or recv.mcasttype isa MNullType then
add("if ({recv} == null || {recv}.is_null()) \{")
if mmethod.name == "==" or mmethod.name == "is_same_instance" then
if res == null then res = new_var(bool_type)
var arg = arguments[1]
if arg.mcasttype isa MNullableType then
add("{res} = ({arg} == null || {arg}.is_null());")
else if arg.mcasttype isa MNullType then
add("{res} = true; /* is null */")
else
add("{res} = false; /* {arg.inspect} cannot be null */")
end
else if mmethod.name == "!=" then
if res == null then res = new_var(bool_type)
# res = self.new_var(bool_type)
var arg = arguments[1]
if arg.mcasttype isa MNullableType then
add("{res} = ({arg} != null && !{arg}.is_null());")
else if arg.mcasttype isa MNullType then
add("{res} = false; /* is null */")
else
add("{res} = true; /* {arg.inspect} cannot be null */")
end
else
add_abort("Receiver is null")
ret(null_instance)
end
add("\} else \{")
else
add "\{"
add "/* recv ({recv}) cannot be null since it's a {recv.mcasttype}"
end
if consider_null then
var arg = arguments[1]
if arg.mcasttype isa MNullType then
if res == null then res = new_var(bool_type)
if mmethod.name == "!=" then
add("{res} = true; /* arg is null and recv is not */")
else # `==` and `is_same_instance`
add("{res} = false; /* arg is null but recv is not */")
end
add("\}") # closes the null case
add("if (false) \{") # what follow is useless, Javac will drop it
end
end
end
# Perform a method call through vft
private fun table_send(mmethod: TableCallable, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
var mdef: MMethodDef
var name: String
if mmethod isa MMethod then
mdef = mmethod.intro
name = mmethod.full_name
else if mmethod isa MMethodDef then
mdef = mmethod
name = mmethod.full_name
else
abort
end
var recv = arguments.first
var rect = mdef.mclassdef.bound_mtype
var msignature = mdef.msignature.as(not null)
msignature = msignature.resolve_for(rect, rect, compiler.mainmodule, true)
adapt_signature(mdef, arguments)
var res: nullable RuntimeVariable
var ret = msignature.return_mtype
if ret == null then
res = null
else
res = self.new_var(ret)
end
before_send(res, mdef, arguments)
add "/* concrete call to {mdef} */"
if res != null then
var ress = new_expr("{recv}.rtclass.vft.get(\"{name}\").exec(new RTVal[]\{{arguments.join(",")}\});", compiler.mainmodule.object_type)
assign(res, ress)
else
add("{recv}.rtclass.vft.get(\"{name}\").exec(new RTVal[]\{{arguments.join(",")}\});")
end
add("\}") # closes the null case
return res
end
# Generate a super call from a method definition
fun supercall(m: MMethodDef, recvtype: MClassType, args: Array[RuntimeVariable]): nullable RuntimeVariable do
return table_send(m, args)
end
# Generate a monomorphic send for the method `m`, the type `t` and the arguments `args`
fun monomorphic_send(m: MMethod, t: MType, args: Array[RuntimeVariable]): nullable RuntimeVariable do
assert t isa MClassType
var propdef = m.lookup_first_definition(self.compiler.mainmodule, t)
return self.static_call(propdef, args)
end
# Code generation
# Add a line (will be suffixed by `\n`)
fun add(line: String) do file.lines.add("{line}\n")
# Add a new partial line (no `\n` suffix)
fun addn(line: String) do file.lines.add(line)
# Compile a statement (if any)
fun stmt(nexpr: nullable AExpr) do
if nexpr == null then return
if nexpr.mtype == null and not nexpr.is_typed then
# Untyped expression.
# Might mean dead code or invalid code
# so aborts
add_abort("FATAL: bad statement executed.")
return
end
var old = self.current_node
current_node = nexpr
nexpr.stmt(self)
current_node = old
end
# Compile an expression an return its result
# `mtype` is the expected return type, pass null if no specific type is expected.
fun expr(nexpr: AExpr, mtype: nullable MType): RuntimeVariable do
var old = current_node
current_node = nexpr
var res = null
if nexpr.mtype != null then
res = nexpr.expr(self)
end
if res == null then
# Untyped expression.
# Might mean dead code or invalid code.
# so aborts
add_abort("FATAL: bad expression executed.")
# and return a placebo result to please the C compiler
if mtype == null then mtype = compiler.mainmodule.object_type
res = null_instance
self.current_node = old
return res
end
if mtype != null then
mtype = anchor(mtype)
res = autobox(res, mtype)
end
current_node = old
return res
end
# Alias for `self.expr(nexpr, self.bool_type)`
fun expr_bool(nexpr: AExpr): RuntimeVariable do
return expr(nexpr, compiler.mainmodule.bool_type)
end
# Correctly assign a left and a right value
# Boxing and unboxing is performed if required
fun assign(left, right: RuntimeVariable) do
add("{left} = {autobox(right, left.mtype)};")
end
# Generate a return with `value`
fun ret(value: RuntimeVariable) do
var frame = self.frame
assert frame != null
var returnvar = frame.returnvar
if returnvar != null then
assign(returnvar, value)
end
self.add("break {frame.returnlabel.as(not null)};")
end
# Return a new local RuntimeVariable initialized with the Java expression `jexpr`.
#
# `mtype` is used for the Java return variable initialization.
fun new_expr(jexpr: String, mtype: MType): RuntimeVariable do
var res = new_var(mtype)
add("{res} = {jexpr};")
return res
end
# Generate generic abort
#
# Used by aborts, asserts, casts, etc.
fun add_abort(message: String) do
add("System.err.print(\"Runtime error: {message}\");")
add_raw_abort
end
# Abort without displaying the cause.
#
# Used to customizable errors.
private fun add_raw_abort do
var node = current_node
if node != null then
add("System.err.print(\" ({node.location.short_location})\");")
end
add("System.err.println(\"\");")
add("System.exit(1);")
end
# Add a dynamic cast
fun add_cast(value: RuntimeVariable, mtype: MType) do
var res = type_test(value, mtype)
add("if (!{res}) \{")
add("System.err.print(\"Runtime error: Cast failed. Expected `{mtype.to_s.escape_to_c}`, got `\" + {value}.rtclass.class_name + \"`\");")
add_raw_abort
add("\}")
end
# Types handling
# Anchor a type to the main module and the current receiver
fun anchor(mtype: MType): MType do
if not mtype.need_anchor then return mtype
return mtype.anchor_to(compiler.mainmodule, frame.as(not null).receiver)
end
# Adapt the arguments of a method according to targetted `MMethodDef`
fun adapt_signature(m: MMethodDef, args: Array[RuntimeVariable]) do
var msignature = m.msignature.as(not null).resolve_for(
m.mclassdef.bound_mtype,
m.mclassdef.bound_mtype,
m.mclassdef.mmodule, true)
args.first = autobox(args.first, compiler.mainmodule.object_type)
for i in [0..msignature.arity[ do
args[i+1] = autobox(args[i + 1], compiler.mainmodule.object_type)
end
end
# Box primitive `value` to `mtype`.
private fun box(value: RuntimeVariable, mtype: MType): RuntimeVariable do
if value.is_boxed then return value
var obj_type = compiler.mainmodule.object_type
if value.mtype isa MNullType then
return new_expr("new RTVal(null, null)", compiler.mainmodule.model.null_type)
end
var mbox = value.mtype.as(MClassType).mclass
return new_expr("new RTVal({mbox.rt_name}.get{mbox.rt_name}(), {value})", obj_type)
end
# Unbox primitive `value` to `mtype`.
private fun unbox(value: RuntimeVariable, mtype: MType): RuntimeVariable do
if not value.is_boxed then return value
if not mtype.is_java_primitive then return value
if compiler.box_kinds.has(mtype) then
return new_expr("({mtype.java_type}){value}.value", mtype)
else
info "NOT YET IMPLEMENTED unbox for {value} ({mtype})"
abort
end
end
# Box or unbox primitive `value` to `mtype` if needed.
private fun autobox(value: RuntimeVariable, mtype: MType): RuntimeVariable do
if mtype.is_java_primitive then return unbox(value, mtype)
return box(value, mtype)
end
# Can this `value` be a primitive Java value?
private fun can_be_primitive(value: RuntimeVariable): Bool do
var t = value.mcasttype.undecorate
if not t isa MClassType then return false
var k = t.mclass.kind
return k == interface_kind or t.is_java_primitive
end
# Generate a polymorphic subtype test
fun type_test(value: RuntimeVariable, mtype: MType): RuntimeVariable do
add("/* {value.inspect} isa {mtype} */")
var res = self.new_var(compiler.mainmodule.bool_type)
# check color is in table
var maybenull = (value.mcasttype isa MNullableType or value.mcasttype isa MNullType)
if maybenull then
add("if({value} == null || {value}.is_null()) \{")
add("{res} = true && {mtype isa MNullableType};")
add("\} else \{")
end
if mtype isa MNullableType then mtype = mtype.mtype
var mclass = mtype.as(MClassType).mclass
add("{res} = {value}.rtclass.supers.get(\"{mclass.jname}\") == {mclass.rt_name}.get{mclass.rt_name}();")
if maybenull then
add("\}")
end
return res
end
# Generate the code required to dynamically check if 2 objects share the same runtime type
fun is_same_type_test(value1, value2: RuntimeVariable): RuntimeVariable do
var res = self.new_var(compiler.mainmodule.bool_type)
add("{res} = {value1}.rtclass == {value2}.rtclass;")
return res
end
# Native instances
# Generate an integer value
fun int_instance(value: Int): RuntimeVariable do
var t = compiler.mainmodule.int_type
return new RuntimeVariable(value.to_s, t, t)
end
# Generate a byte value
fun byte_instance(value: Byte): RuntimeVariable do
var t = compiler.mainmodule.byte_type
return new RuntimeVariable(value.to_s, t, t)
end
# Generate a char value
fun char_instance(value: Char): RuntimeVariable do
var t = compiler.mainmodule.char_type
return new RuntimeVariable("'{value.to_s.escape_to_c}'", t, t)
end
# Generate a float value
#
# FIXME pass a Float, not a string
fun float_instance(value: String): RuntimeVariable do
var t = compiler.mainmodule.float_type
return new RuntimeVariable(value.to_s, t, t)
end
# Generate an integer value
fun bool_instance(value: Bool): RuntimeVariable do
var t = compiler.mainmodule.bool_type
return new RuntimeVariable(value.to_s, t, t)
end
# Generate the `null` value
fun null_instance: RuntimeVariable do
var t = compiler.mainmodule.model.null_type
return new RuntimeVariable("null", t, t)
end
# Get an instance of a array for a vararg
fun vararg_instance(mpropdef: MPropDef, recv: RuntimeVariable, varargs: Array[RuntimeVariable], elttype: MType): RuntimeVariable do
# TODO handle dynamic types
info("NOT YET IMPLEMENTED vararg_instance")
return null_instance
# TODO return array_instance(varargs, elttype)
end
# Nit instances
# Generate a alloc-instance + init-attributes
fun init_instance(mtype: MClassType): RuntimeVariable do
var rt_name = mtype.mclass.rt_name
var res = new_expr("new RTVal({rt_name}.get{rt_name}())", mtype)
generate_init_attr(self, res, mtype)
return res
end
# Generate code that initialize the attributes on a new instance
fun generate_init_attr(v: JavaCompilerVisitor, recv: RuntimeVariable, mtype: MClassType) do
var cds = mtype.collect_mclassdefs(v.compiler.mainmodule).to_a
v.compiler.mainmodule.linearize_mclassdefs(cds)
for cd in cds do
for npropdef in v.compiler.modelbuilder.collect_attr_propdef(cd) do
npropdef.init_expr(v, recv)
end
end
end
# Generate a Nit "is" for two runtime_variables
fun equal_test(value1, value2: RuntimeVariable): RuntimeVariable do
var res = new_var(compiler.mainmodule.bool_type)
if value2.mtype.is_java_primitive and not value1.mtype.is_java_primitive then
var tmp = value1
value1 = value2
value2 = tmp
end
if value1.mtype.is_java_primitive then
if value2.mtype == value1.mtype then
add("{res} = {value1} == {value2}; /* == with two primitives */")
else if value2.mtype.is_java_primitive then
add("{res} = true; /* incompatible types {value1.mtype} vs. {value2.mtype}*/")
# else if value1.mtype.is_tagged then
# add("{res} = ({value2} != NULL) && ({autobox(value2, value1.mtype)} == {value1});")
else
var rt_name = value1.mtype.as(MClassType).mclass.rt_name
add("{res} = ({value2} != null) && ({value2}.rtclass == {rt_name}.get{rt_name}());")
add("if ({res}) \{")
add("{res} = ({self.autobox(value2, value1.mtype)} == {value1});")
add("\}")
end
return res
end
var maybe_null = true
var test = new Array[String]
var t1 = value1.mcasttype
if t1 isa MNullableType then
test.add("{value1} != null && !{value1}.is_null()")
t1 = t1.mtype
else
maybe_null = false
end
var t2 = value2.mcasttype
if t2 isa MNullableType then
test.add("{value2} != null && !{value2}.is_null()")
t2 = t2.mtype
else
maybe_null = false
end
var incompatible = false
var primitive
if t1.is_java_primitive then
primitive = t1
if t1 == t2 then
# No need to compare class
else if t2.is_java_primitive then
incompatible = true
else if can_be_primitive(value2) then
if t1.is_java_primitive then
self.add("{res} = {value1} == {value2}; /* t1 is primitive and t2 can be */")
return res
end
# if not compiler.modelbuilder.toolcontext.opt_no_tag_primitives.value then
# test.add("(!{extract_tag(value2)})")
# end
test.add("{value1}.rtclass == {value2}.rtclass")
else
incompatible = true
end
else if t2.is_java_primitive then
primitive = t2
if can_be_primitive(value1) then
if t2.is_java_primitive then
self.add("{res} = {value1} == {value2}; /* t2 is primitive and t1 can be */")
return res
end
test.add("{value1}.rtclass == {value2}.rtclass")
else
incompatible = true
end
else
primitive = null
end
if incompatible then
if maybe_null then
self.add("{res} = {value1} == {value2}; /* incompatible types {t1} vs. {t2}; but may be NULL*/")
return res
else
self.add("{res} = false; /* incompatible types {t1} vs. {t2}; cannot be NULL */")
return res
end
end
if primitive != null then
if primitive.is_java_primitive then
self.add("{res} = {value1} == {value2};")
return res
end
test.add("({value1}.value == {value2}.value")
else if can_be_primitive(value1) and can_be_primitive(value2) then
test.add("{value1}.rtclass == {value2}.rtclass")
var s = new Array[String]
for b in compiler.box_kinds do
var rt_name = b.mclass.rt_name
s.add "({value1}.rtclass == {rt_name}.get{rt_name}()) && ({value1}.value.equals({value2}.value))"
if b.mclass.name == "Float" then
s.add "({value1}.rtclass == RTClass_kernel_Float.getRTClass_kernel_Float() && {value1}.rtclass == {value2}.rtclass && Math.abs((double)({value1}.value)) == 0.0 && Math.abs((double)({value2}.value)) == 0.0)"
end
end
if s.is_empty then
self.add("{res} = {value1} == {value2}; /* both can be primitive */")
return res
end
test.add("({s.join(" || ")})")
else
self.add("{res} = {value1} == {value2}; /* no primitives */")
return res
end
self.add("{res} = {value1} == {value2} || ({test.join(" && ")});")
return res
end
# Attributes
# Generate a polymorphic attribute is_set test
fun isset_attribute(a: MAttribute, recv: RuntimeVariable): RuntimeVariable do
# TODO self.check_recv_notnull(recv)
var res = new_var(compiler.mainmodule.bool_type)
# What is the declared type of the attribute?
var mtype = a.intro.static_mtype.as(not null)
var intromclassdef = a.intro.mclassdef
mtype = mtype.resolve_for(intromclassdef.bound_mtype, intromclassdef.bound_mtype, intromclassdef.mmodule, true)
if mtype isa MNullableType then
add("{res} = true; /* easy isset: {a} on {recv.inspect} */")
return res
end
add("{res} = {recv}.attrs.get(\"{a.jname}\") != null; /* {a} on {recv.inspect} */")
return res
end
# Generate a polymorphic attribute read
fun read_attribute(a: MAttribute, recv: RuntimeVariable): RuntimeVariable do
# TODO check_recv_notnull(recv)
# TODO compile_check(v)
# What is the declared type of the attribute?
var ret = a.intro.static_mtype.as(not null)
var intromclassdef = a.intro.mclassdef
ret = ret.resolve_for(intromclassdef.bound_mtype, intromclassdef.bound_mtype, intromclassdef.mmodule, true)
# Check for Uninitialized attribute
if not ret isa MNullableType then check_attribute(a, recv)
return new_expr("{recv}.attrs.get(\"{a.jname}\")", ret)
end
# Generate a polymorphic attribute write
fun write_attribute(a: MAttribute, recv: RuntimeVariable, value: RuntimeVariable) do
# TODO check_recv_notnull(recv)
add "{recv}.attrs.put(\"{a.jname}\", {autobox(value, compiler.mainmodule.object_type)});"
end
# Check uninitialized attribute
fun check_attribute(a: MAttribute, recv: RuntimeVariable) do
add "if({recv}.attrs.get(\"{a.jname}\") == null) \{"
add_abort "Uninitialized attribute {a.name}"
add "\}"
end
# Utils
# Display a info message
fun info(str: String) do compiler.modelbuilder.toolcontext.info(str, 0)
end
src/compiler/java_compiler.nit:357,1--1131,3