using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace Magpie.Compilation { /// /// Expression visitor that compiles expressions down to bytecode. /// public class BytecodeGenerator : IBoundExprVisitor { public int Position { get { return (int)mWriter.BaseStream.Position; } } private BytecodeGenerator(Compiler compiler, BinaryWriter writer, OffsetTable functionPatcher, StringTable stringTable) { mCompiler = compiler; mWriter = writer; mFunctionPatcher = functionPatcher; mStrings = stringTable; mJumpTable = new JumpTable(this); } public static void Generate(Compiler compiler, BinaryWriter writer, OffsetTable functionPatcher, StringTable stringTable, Function function) { BytecodeGenerator generator = new BytecodeGenerator(compiler, writer, functionPatcher, stringTable); functionPatcher.DefineOffset(function.UniqueName()); writer.Write(function.NumLocals); function.Body.Bound.Accept(generator); generator.TranslateTailCall(); generator.Write(OpCode.Return); } public void SeekTo(int position) { mWriter.Seek(position, SeekOrigin.Begin); } public void SeekToEnd() { mWriter.Seek(0, SeekOrigin.End); } #region IBoundExprVisitor Members bool IBoundExprVisitor.Visit(UnitExpr expr) { return true; } // do nothing bool IBoundExprVisitor.Visit(BoolExpr expr) { Write(OpCode.PushBool, expr.Value ? (byte)1 : (byte)0); return true; } bool IBoundExprVisitor.Visit(IntExpr expr) { Write(OpCode.PushInt, expr.Value); return true; } bool IBoundExprVisitor.Visit(StringExpr expr) { Write(OpCode.PushString); mStrings.InsertOffset(expr.Value); return true; } bool IBoundExprVisitor.Visit(BoundFuncRefExpr expr) { Write(OpCode.PushInt); mFunctionPatcher.InsertOffset(expr.Function.UniqueName()); return true; } bool IBoundExprVisitor.Visit(ForeignCallExpr expr) { // evaluate the arg expr.Arg.Accept(this); // add the foreign call OpCode op; switch (expr.Function.FuncType.Parameter.Bound.Expand().Length) { case 0: op = OpCode.ForeignCall0; break; case 1: op = OpCode.ForeignCall1; break; default: op = OpCode.ForeignCallN; break; } Write(op, expr.Function.ID); return true; } bool IBoundExprVisitor.Visit(BoundTupleExpr tuple) { // must visit in forward order to ensure that function arguments are // evaluated left to right foreach (var field in tuple.Fields) { field.Accept(this); } // create the structure Write(OpCode.Alloc, tuple.Fields.Count); return true; } bool IBoundExprVisitor.Visit(IntrinsicExpr expr) { expr.Arg.Accept(this); expr.Intrinsic.OpCodes.ForEach(op => Write(op)); return true; } bool IBoundExprVisitor.Visit(BoundCallExpr expr) { expr.Arg.Accept(this); expr.Target.Accept(this); // add the call OpCode op; switch (expr.Arg.Type.Expand().Length) { case 0: op = OpCode.Call0; break; case 1: op = OpCode.Call1; break; default: op = OpCode.CallN; break; } mLastCallPosition = Position; mLastCall = op; Write(op); return true; } bool IBoundExprVisitor.Visit(BoundArrayExpr expr) { // the count is the first element Write(OpCode.PushInt, expr.Elements.Count); // must visit in forward order to ensure that array elements are // evaluated left to right foreach (var element in expr.Elements) { element.Accept(this); } // create the structure (+1 for the size) Write(OpCode.Alloc, expr.Elements.Count + 1); return true; } bool IBoundExprVisitor.Visit(BoundBlockExpr block) { block.Exprs.ForEach(expr => expr.Accept(this)); return true; } bool IBoundExprVisitor.Visit(BoundIfThenExpr expr) { // evaluate the condition expr.Condition.Accept(this); mJumpTable.JumpIfFalse("end"); // execute the body expr.Body.Accept(this); // jump past it mJumpTable.PatchJump("end"); return true; } bool IBoundExprVisitor.Visit(BoundIfThenElseExpr expr) { // evaluate the condition expr.Condition.Accept(this); mJumpTable.JumpIfFalse("else"); // thenBody expr.ThenBody.Accept(this); // jump to end mJumpTable.Jump("end"); // elseBody mJumpTable.PatchJump("else"); expr.ElseBody.Accept(this); // end mJumpTable.PatchJump("end"); return true; } bool IBoundExprVisitor.Visit(BoundReturnExpr expr) { expr.Value.Accept(this); TranslateTailCall(); Write(OpCode.Return); return true; } bool IBoundExprVisitor.Visit(BoundWhileExpr expr) { mJumpTable.PatchJumpBack("while"); // evaluate the condition expr.Condition.Accept(this); mJumpTable.JumpIfFalse("end"); // body expr.Body.Accept(this); // jump back to loop mJumpTable.JumpBack("while"); // exit loop mJumpTable.PatchJump("end"); return true; } bool IBoundExprVisitor.Visit(LoadExpr expr) { expr.Struct.Accept(this); Write(OpCode.Load, (byte)expr.Index); return true; } bool IBoundExprVisitor.Visit(StoreExpr expr) { expr.Value.Accept(this); expr.Struct.Accept(this); Write(OpCode.Store, expr.Field.Index); return true; } bool IBoundExprVisitor.Visit(LocalsExpr expr) { Write(OpCode.PushLocals); return true; } bool IBoundExprVisitor.Visit(ConstructExpr expr) { expr.Arg.Accept(this); // if the struct has only one field, the arg tuple will just be a // value. in that case, we need to hoist it into a struct to make // it properly a reference type. //### opt: this is really only needed for mutable single-field // structs. for immutable ones, pass by reference and pass by value // are indistinguishable. if (expr.Struct.Fields.Count == 1) { Write(OpCode.Alloc, 1); } else if (expr.Struct.Fields.Count == 0) { // for an empty struct, just include a dummy value //### bob: this is gross Write(OpCode.PushInt, 0); } // note that if there is more than one field, this evaluates to // nothing: the arg tuple for the structure *is* the structure // so the work is already done. return true; } bool IBoundExprVisitor.Visit(ConstructUnionExpr expr) { // push the case tag Write(OpCode.PushInt, expr.Case.Index); // evaluate the value expr.Arg.Accept(this); // one slot for the case tag int numSlots = 1; // load the value (if any) if (expr.Case.ValueType.Bound != Decl.Unit) { // add a slot for the value numSlots++; } // create the structure Write(OpCode.Alloc, numSlots); return true; } #endregion public void Write(OpCode op) { mWriter.Write((byte)op); } public void Write(int value) { mWriter.Write(value); } public void Write(OpCode op, byte operand) { Write(op); Write(operand); } public void Write(OpCode op, int operand) { Write(op); Write(operand); } private void Write(byte value) { mWriter.Write(value); } private void TranslateTailCall() { // if there is a call at the end, translate it to a tail call if (mLastCallPosition == Position - 1) { SeekTo(mLastCallPosition); OpCode op; switch (mLastCall) { case OpCode.Call0: op = OpCode.TailCall0; break; case OpCode.Call1: op = OpCode.TailCall1; break; default: op = OpCode.TailCallN; break; } Write(op); SeekToEnd(); } } private Compiler mCompiler; private BinaryWriter mWriter; private OffsetTable mFunctionPatcher; private StringTable mStrings; private JumpTable mJumpTable; private int mLastCallPosition; private OpCode mLastCall; } }