diff options
author | Iulian Dragos <jaguarul@gmail.com> | 2006-02-14 12:50:40 +0000 |
---|---|---|
committer | Iulian Dragos <jaguarul@gmail.com> | 2006-02-14 12:50:40 +0000 |
commit | 2197e9485a681f72068c5768263bcd1757664775 (patch) | |
tree | 3bc2b962af4209f182925724ec7f089ca91a3bb9 /src | |
parent | 9392e582986c3d54cde992c3141b52c97f7d6e16 (diff) | |
download | scala-2197e9485a681f72068c5768263bcd1757664775.tar.gz scala-2197e9485a681f72068c5768263bcd1757664775.tar.bz2 scala-2197e9485a681f72068c5768263bcd1757664775.zip |
Refactored ICode and added basic inlining capab...
Refactored ICode and added basic inlining capabilities.
Diffstat (limited to 'src')
14 files changed, 278 insertions, 53 deletions
diff --git a/src/compiler/scala/tools/nsc/CompilerRun.scala b/src/compiler/scala/tools/nsc/CompilerRun.scala index 7677fc2cb8..94b5be7409 100644 --- a/src/compiler/scala/tools/nsc/CompilerRun.scala +++ b/src/compiler/scala/tools/nsc/CompilerRun.scala @@ -15,6 +15,7 @@ class CompilerRun { def erasurePhase: Phase = NoPhase; def flattenPhase: Phase = NoPhase; def mixinPhase: Phase = NoPhase; + def icodePhase: Phase = NoPhase; def phaseNamed(name: String): Phase = NoPhase; } diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 2f41ae9194..35373f9914 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -25,6 +25,7 @@ import transform._; import backend.icode.{ICodes, GenICode, Checkers}; import backend.ScalaPrimitives; import backend.jvm.GenJVM; +import backend.opt.Inliners; class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable with Trees @@ -267,6 +268,10 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable val global: Global.this.type = Global.this; } + object inliner extends Inliners { + val global: Global.this.type = Global.this + } + object genJVM extends GenJVM { val global: Global.this.type = Global.this; } @@ -293,6 +298,7 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable flatten, mixer, genicode, + inliner, genJVM, sampleTransform); @@ -350,6 +356,7 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable override val erasurePhase = phaseNamed("erasure"); override val flattenPhase = phaseNamed("flatten"); override val mixinPhase = phaseNamed("mixin"); + override val icodePhase = phaseNamed("icode"); private var unitbuf = new ListBuffer[CompilationUnit]; private var fileset = new HashSet[AbstractFile]; @@ -393,7 +400,9 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable val startTime = System.currentTimeMillis(); phase = globalPhase; globalPhase.run; - if (settings.print contains globalPhase.name) treePrinter.printAll(); + if (settings.print contains globalPhase.name) + if (globalPhase.id >= icodePhase.id) writeICode() + else treePrinter.printAll(); if (settings.browse contains globalPhase.name) treeBrowser.browse(units); informTime(globalPhase.description, startTime); globalPhase = globalPhase.next; @@ -408,7 +417,6 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable if (settings.Xshowcls.value != "") showDef(newTermName(settings.Xshowcls.value), false); if (settings.Xshowobj.value != "") showDef(newTermName(settings.Xshowobj.value), true); - if (settings.Xshowicode.value) writeICode(); if (reporter.errors == 0) { assert(symData.isEmpty, symData.elements.toList); @@ -513,9 +521,11 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable } private def writeICode(): Unit = { - val printer = new icodePrinter.TextPrinter(null); - icodes.classes.foreach((cls) => { - val file = getFile(cls.symbol, ".icode"); + val printer = new icodePrinter.TextPrinter(null, icodes.linearizer); + icodes.classes.values.foreach((cls) => { + var file = getFile(cls.symbol, ".icode"); + if (file.exists()) + file = new File(file.getParentFile(), file.getName() + "1"); try { val stream = new FileOutputStream(file); printer.setWriter(new PrintWriter(stream, true)); diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala index 0ce0b34a07..0a1d10d8ae 100644 --- a/src/compiler/scala/tools/nsc/Settings.scala +++ b/src/compiler/scala/tools/nsc/Settings.scala @@ -18,7 +18,7 @@ class Settings(error: String => unit) { concatPath( getProperty("java.class.path"), getProperty("scala.class.path")), - ".") + "") private val bootclasspathDefault = alternatePath( @@ -57,7 +57,7 @@ class Settings(error: String => unit) { val noassertions = BooleanSetting("-noassert", "Generate no assertions and assumptions") val verbose = BooleanSetting("-verbose", "Output messages about what the compiler is doing") val classpath = StringSetting ("-classpath", "path", "Specify where to find user class files", classpathDefault) - val sourcepath = StringSetting ("-sourcepath", "path", "Specify where to find input source files", ".") + val sourcepath = StringSetting ("-sourcepath", "path", "Specify where to find input source files", "") val bootclasspath = StringSetting ("-bootclasspath", "path", "Override location of bootstrap class files", bootclasspathDefault) val extdirs = StringSetting ("-extdirs", "dirs", "Override location of installed extensions", extdirsDefault) val outdir = StringSetting ("-d", "directory", "Specify where to place generated class files", ".") @@ -84,6 +84,7 @@ class Settings(error: String => unit) { val log = PhasesSetting ("-log", "Log operations in") val version = BooleanSetting("-version", "Print product version and exit") val help = BooleanSetting("-help", "Print a synopsis of standard options") + val inline = BooleanSetting("-inline", "Perform inlining when possible") val Xshowcls = StringSetting ("-Xshowcls", "class", "Show class info", "") val Xshowobj = StringSetting ("-Xshowobj", "object", "Show object info", "") diff --git a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala index 83128a8274..aa42da4273 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala @@ -57,7 +57,10 @@ mixin class BasicBlocks requires ICodes { /** Apply a function to all the instructions of the block. */ def traverse(f: Instruction => unit) = { - assert(closed, "Traversing an open block!: "); + if (!closed) { + dump; + global.abort("Traversing an open block!: " + label); + } instrs foreach f; } @@ -160,7 +163,6 @@ mixin class BasicBlocks requires ICodes { assert (!closed || ignore, "BasicBlock closed"); if (!ignore) { -// Console.println("block " + label + ": " + instr); instr.pos = pos; instructionList = instr :: instructionList; _lastInstruction = instr; @@ -172,7 +174,19 @@ mixin class BasicBlocks requires ICodes { assert(instructionList.length > 0, "Empty block."); closed = true; - instrs = toInstructionArray(instructionList.reverse); + instructionList = instructionList.reverse; + instrs = toInstructionArray(instructionList); + } + + def open = { + closed = false; + } + + def instructions: List[Instruction] = instructionList; + + def clear: Unit = { + instructionList = Nil; + instrs = null; } def isEmpty: Boolean = instructionList.isEmpty; @@ -222,8 +236,10 @@ mixin class BasicBlocks requires ICodes { case RETURN(_) => Nil; case THROW() => Nil; case _ => - if (isClosed) + if (isClosed) { + dump; global.abort("The last instruction is not a control flow instruction: " + lastInstruction); + } else Nil; } diff --git a/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala b/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala index 00741a1e0e..0963a89bc9 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala @@ -59,7 +59,7 @@ abstract class Checkers { def checkICodes: Unit = { Console.println("[[consistency check at beginning of phase " + globalPhase.name + "]]"); - classes foreach check; + classes.values foreach check; } def check(cls: IClass): Unit = { diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 553a797f93..23b1584c82 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -75,7 +75,7 @@ abstract class GenICode extends SubComponent { log("Generating class: " + tree.symbol.fullNameString); ctx setClass (new IClass(tree.symbol) setCompilationUnit unit); addClassFields(ctx, tree.symbol); - classes = ctx.clazz :: classes; + classes += tree.symbol -> ctx.clazz; gen(impl, ctx); ctx setClass null; @@ -83,13 +83,6 @@ abstract class GenICode extends SubComponent { case ModuleDef(mods, name, impl) => abort("Modules should not reach backend!"); - log("Generating module: " + tree.symbol.fullNameString); - ctx setClass (new IClass(tree.symbol) setCompilationUnit unit); - addClassFields(ctx, tree.symbol); - classes = ctx.clazz :: classes; - gen(impl, ctx); - ctx setClass null; - case ValDef(mods, name, tpt, rhs) => ctx; // we use the symbol to add fields case DefDef(mods, name, tparams, vparamss, tpt, rhs) => @@ -646,7 +639,7 @@ abstract class GenICode extends SubComponent { log("synchronized block start"); ctx1 = ctx1.Try( bodyCtx => { - val ctx1 = genLoad(args.head, bodyCtx, toTypeKind(tree.tpe.resultType)); + val ctx1 = genLoad(args.head, bodyCtx, expectedType /* toTypeKind(tree.tpe.resultType) */); ctx1.bb.emit(LOAD_LOCAL(monitor, false)); ctx1.bb.emit(MONITOR_EXIT(), tree.pos); ctx1 @@ -655,6 +648,7 @@ abstract class GenICode extends SubComponent { exhCtx.bb.emit(LOAD_LOCAL(monitor, false)); exhCtx.bb.emit(MONITOR_EXIT(), tree.pos); exhCtx.bb.emit(THROW()); + exhCtx.bb.enterIgnoreMode; exhCtx }))); if (settings.debug.value) @@ -1303,20 +1297,21 @@ abstract class GenICode extends SubComponent { if (block.size == 1 && optCont != None) { val Some(cont) = optCont; val pred = block.predecessors; - log("Preds: " + pred + " of " + block); + log("Preds: " + pred + " of " + block + " (" + optCont + ")"); pred foreach { p => p.lastInstruction match { case CJUMP(succ, fail, cond, kind) => if (settings.debug.value) log("Pruning empty if branch."); changed = true; - p.replaceInstruction(p.lastInstruction, + assert(p.replaceInstruction(p.lastInstruction, if (block == succ) CJUMP(cont, fail, cond, kind) else if (block == fail) CJUMP(succ, cont, cond, kind) else - abort("Could not find block in preds")); + abort("Could not find block in preds")), + "Didn't find p.lastInstruction"); case CZJUMP(succ, fail, cond, kind) => if (settings.debug.value) @@ -1334,7 +1329,8 @@ abstract class GenICode extends SubComponent { if (settings.debug.value) log("Pruning empty if branch."); changed = true; - p.replaceInstruction(p.lastInstruction, JUMP(cont)); + assert(p.replaceInstruction(p.lastInstruction, JUMP(cont)), + "Didn't find p.lastInstruction"); case SWITCH(tags, labels) => if (settings.debug.value) @@ -1632,6 +1628,8 @@ abstract class GenICode extends SubComponent { case _ => instr; } } + + override def toString() = symbol.toString(); } ///////////////// Fake instructions ////////////////////////// diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala index 99ecc1b1c2..03c9976437 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala @@ -7,7 +7,10 @@ package scala.tools.nsc.backend.icode; +import java.io.PrintWriter; + import scala.tools.nsc.symtab._; +import scala.collection.mutable.HashMap; /** Glue together ICode parts. */ @@ -24,7 +27,7 @@ abstract class ICodes extends AnyRef val global: Global; /** The ICode representation of classes */ - var classes: List[IClass] = _; + var classes: HashMap[global.Symbol, IClass] = new HashMap(); /** The ICode linearizer. */ val linearizer: Linearizer = @@ -37,6 +40,16 @@ abstract class ICodes extends AnyRef else global.abort("Unknown linearizer: " + global.settings.Xlinearizer.value); - def init = { classes = Nil } + + /** Print all classes and basic blocks. Used for debugging. */ + def dump: Unit = { + val printer = new global.icodePrinter.TextPrinter(new PrintWriter(System.out, true), + new global.icodes.DumpLinearizer()); + + global.icodes.classes.values foreach { c => printer.printClass(c); } + } + + + def init = { } } diff --git a/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala b/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala index 52611fd4d5..ae326c0714 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala @@ -155,4 +155,13 @@ mixin class Linearizers requires ICodes { if (!blocks.contains(b)) blocks = b :: blocks; } + + /** A 'dump' of the blocks in this method, which does not + * require any well-formedness of the basic blocks (like + * the last instruction being a jump). + */ + class DumpLinearizer extends Linearizer { + def linearize(m: IMethod): List[BasicBlock] = + m.code.blocks.toList; + } } diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala index 28826a5439..02af695f37 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Members.scala @@ -99,27 +99,6 @@ mixin class Members requires ICodes { /** This methods returns a string representation of the ICode */ override def toString() : String = "ICode '" + label + "'"; - /** This method print the code */ -// def print() : unit = print(System.out); - -// def print(out: java.io.PrintStream) : unit = { -// traverse((bb: BasicBlock) => { -// out.println("Block #" + bb.label); -// out.println("Substituable variables : "); -// if (bb.substituteVars != null) -// bb.substituteVars.foreach(out.print); -// else -// out.println(" {Empty} "); -// out.println("Instructions:"); -// bb.traverse((ici: Instruction) => -// out.println(" "+ici.toString())); -// out.print ("Successors: "); -// bb.successors.foreach((bb: BasicBlock) => out.print(bb.label+", ")); -// out.println (""); // ?? Del -// out.println (); -// }); -// } - /* Compute a unique new label */ def nextLabel = { currentLabel = currentLabel + 1; @@ -159,6 +138,8 @@ mixin class Members requires ICodes { override def toString() = symbol.fullNameString; def lookupField(s: Symbol) = fields find ((f) => f.symbol == s); + def lookupMethod(s: Symbol) = methods find ((m) => m.symbol == s); + def lookupMethod(s: Name) = methods find ((m) => m.symbol.name == s); } /** Represent a field in ICode */ diff --git a/src/compiler/scala/tools/nsc/backend/icode/Printers.scala b/src/compiler/scala/tools/nsc/backend/icode/Printers.scala index 68ead723d7..7dbb3e1313 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Printers.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Printers.scala @@ -18,7 +18,7 @@ abstract class Printers { import global.icodes.opcodes._; import global.icodes._; - class TextPrinter(writer: PrintWriter) { + class TextPrinter(writer: PrintWriter, lin: Linearizer) { var margin = 0; var out = writer; @@ -88,7 +88,7 @@ abstract class Printers { println(" {"); println("locals: " + m.locals.mkString("", ", ", "")); println; - linearizer.linearize(m) foreach printBlock; + lin.linearize(m) foreach printBlock; println("}"); indent;println("Exception handlers: "); @@ -114,7 +114,7 @@ abstract class Printers { def printBlock(bb: BasicBlock): Unit = { print(bb.label); print(": "); indent; println; - bb traverse printInstruction; + bb.instructions foreach printInstruction; undent; println; } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index b4f4fd87f2..7b56547433 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -36,8 +36,10 @@ abstract class GenJVM extends SubComponent { override def erasedTypes = true; val codeGenerator = new BytecodeGenerator; - override def run: Unit = - classes foreach codeGenerator.genClass; + override def run: Unit = { + if (settings.debug.value) inform("[running phase " + name + " on icode]"); + classes.values foreach codeGenerator.genClass; + } override def apply(unit: CompilationUnit): Unit = abort("JVM works on icode classes, not on compilation units!"); diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala new file mode 100644 index 0000000000..b60ab3055c --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala @@ -0,0 +1,190 @@ +/* NSC -- new scala compiler + * Copyright 2005 LAMP/EPFL + * @author Iulian Dragos + */ + +// $Id: $ + +package scala.tools.nsc.backend.opt; + +import scala.collection.mutable.{Map, HashMap}; +import scala.tools.nsc.symtab._; + +/** + */ +abstract class Inliners extends SubComponent { + import global._; + import icodes._; + import icodes.opcodes._; + + val phaseName = "inliner"; + + /** Create a new phase */ + override def newPhase(p: Phase) = new InliningPhase(p); + + /** The Inlining phase. + */ + class InliningPhase(prev: Phase) extends GlobalPhase(prev) { + def name = phaseName; + override def newFlags = phaseNewFlags; + + override def erasedTypes = true; + val inliner = new Inliner; + + override def run: Unit = { + if (settings.debug.value) inform("[running phase " + name + " on icode]"); + classes.values foreach inliner.analyzeClass; + } + override def apply(unit: CompilationUnit): Unit = + abort("Inlining works on icode classes, not on compilation units!"); + } + + /** + * Simple inliner. + * + */ + class Inliner { + + def inline(caller: IMethod, + block: BasicBlock, + instr: Instruction, + callee: IMethod): Unit = { + if (settings.debug.value) + log("Inlining " + callee + " in " + caller + " at pos: " + + classes(caller.symbol.owner).cunit.position(instr.pos)); + + /* The exception handlers that are active at the current block. */ + val activeHandlers = caller.exh.filter(.covered.contains(block)); + + /* Map 'original' blocks to the ones inlined in the caller. */ + val inlinedBlock: Map[BasicBlock, BasicBlock] = new HashMap; + + val instrBefore = block.instructions.takeWhile( i => i != instr); + val instrAfter = block.instructions.drop(instrBefore.length + 1); + + if (settings.debug.value) { + log("instrBefore: " + instrBefore); + log("instrAfter: " + instrAfter); + } + assert(!instrAfter.isEmpty, "CALL_METHOD cannot be the last instrcution in block!"); + + // store the '$this' into the special local + val inlinedThis = new Local(caller.symbol.newVariable(instr.pos,"$inlThis"), REFERENCE(definitions.ObjectClass)); + + /** Add a new block in the current context. */ + def newBlock = { + val b = caller.code.newBlock; + activeHandlers.foreach (.addBlock(b)); + b + } + + val afterBlock = newBlock; + + /** Map an instruction from the callee to one suitable for the caller. */ + def map(i: Instruction): Instruction = i match { + case THIS(clasz) => + LOAD_LOCAL(inlinedThis, false); + + case LOAD_LOCAL(local, isArg) if (isArg) => + LOAD_LOCAL(local, false); + + case STORE_LOCAL(local, isArg) if (isArg) => + STORE_LOCAL(local, false); + + case JUMP(where) => + JUMP(inlinedBlock(where)); + + case CJUMP(success, failure, cond, kind) => + CJUMP(inlinedBlock(success), inlinedBlock(failure), cond, kind); + + case CZJUMP(success, failure, cond, kind) => + CZJUMP(inlinedBlock(success), inlinedBlock(failure), cond, kind); + + case RETURN(kind) => + JUMP(afterBlock); + + case _ => i + } + + addLocals(caller, callee.locals); + addLocal(caller, inlinedThis); + callee.code.blocks.foreach { b => + if (b != callee.code.startBlock) + inlinedBlock += b -> newBlock; + } + + // re-emit the instructions before the call + block.open; + block.clear; + instrBefore.foreach(block.emit); + + // store the arguments into special locals + callee.params.reverse.foreach { param => + block.emit(STORE_LOCAL(param, false)); + } + block.emit(STORE_LOCAL(inlinedThis, false)); + + // inline the start block of the callee + callee.code.startBlock.traverse { i => + block.emit(map(i), 0); + } + block.close; + + // duplicate the other blocks in the callee + callee.code.traverse { bb => + if (bb != callee.code.startBlock) { + bb.traverse( i => inlinedBlock(bb).emit(map(i), 0) ); + inlinedBlock(bb).close; + } + } + + instrAfter.foreach(afterBlock.emit); + afterBlock.close; + } + + + /** Add a local to this method, performing alfa-renaming + * if necessary. + */ + def addLocals(m: IMethod, ls: List[Local]): Unit = { + m.locals = m.locals ::: ls; + } + + def addLocal(m: IMethod, l: Local): Unit = + m.locals = m.locals ::: List(l); + + val InlineAttr = if (settings.inline.value) global.definitions.getClass("scala.inline").tpe; + + def analyzeClass(cls: IClass): Unit = if (settings.inline.value) { + cls.methods.foreach { m => analyzeMethod(m) + }} + + + def analyzeMethod(m: IMethod): Unit = { + var retry = false; + var count = 0; + + do { + retry = false; + if (m.code ne null) + m.code.traverse { bb => + bb.traverse { i => + if (!retry) + i match { + case CALL_METHOD(msym, _) => + if (definitions.isFunctionType(msym.owner.tpe) + || msym.attributes.exists(a => a._1 == InlineAttr)) + classes(msym.owner).lookupMethod(msym.name) match { + case Some(inc) => + retry = true; + count = count + 1; + inline(m, bb, i, inc); + case None => + log("Couldn't find " + msym.name); + } + case _ => (); + }}} + } while (retry && count < 5); + } + } +} diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index c72bf3beb4..1cd1c757a0 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -33,6 +33,7 @@ class ClassPath(onlyPresentation : Boolean) { // assert(location != null, "cannot find classpath location"); // assert(location.getFile() != null, "cannot find classpath location " + " " + location + " " + location.getClass()); def source : Source; + override def toString() = location.toString(); } @@ -206,6 +207,8 @@ class ClassPath(onlyPresentation : Boolean) { } } } + + override def toString() = entries.toList.mkString("", ":", ""); } // class Build } diff --git a/src/compiler/scala/tools/nsc/util/NameTransformer.scala b/src/compiler/scala/tools/nsc/util/NameTransformer.scala index 982285c5f3..5fe825eb5e 100644 --- a/src/compiler/scala/tools/nsc/util/NameTransformer.scala +++ b/src/compiler/scala/tools/nsc/util/NameTransformer.scala @@ -37,6 +37,7 @@ object NameTransformer { enterOp(':', "$colon"); enterOp('\\', "$bslash"); enterOp('?', "$qmark"); + enterOp('@', "$at"); /** Replace operator symbols by corresponding "$op_name" */ def encode(name: String): String = { |