/// <summary> /// Applies a binary arithmetic intrinsic to an integer value stored /// at an address and a 'one' integer constant. /// </summary> /// <param name="pointer"> /// An address that contains an integer variable to update. /// </param> /// <param name="block"> /// The block to write the update instructions to. /// </param> /// <param name="op"> /// The operator to apply. /// </param> private static void IncrementOrDecrement( ValueTag pointer, BasicBlockBuilder block, string op) { var integerType = ((PointerType)block.Graph.GetValueType(pointer)).ElementType; block.AppendInstruction( Instruction.CreateStore( integerType, pointer, block.AppendInstruction( Instruction.CreateBinaryArithmeticIntrinsic( op, false, integerType, block.AppendInstruction( Instruction.CreateLoad( integerType, pointer)), block.AppendInstruction( Instruction.CreateConstant( new IntegerConstant(1, integerType.GetIntegerSpecOrNull()), integerType)))))); }
private bool TryCompileAssignment(AssignmentSyntax assignment, BasicBlockBuilder builder) { Debug.Assert(_methodInProgress != null); // Get the target variable if (!_variableMap.TryGetVariable(assignment.Variable.Name, out var targetIndex)) { _diagnostics.Add(DiagnosticCode.VariableNotFound, assignment.Variable.Position, assignment.Variable.Name); return(false); } var expectedType = _methodInProgress.Values[targetIndex].Type; // Compile the expression var sourceIndex = ExpressionCompiler.TryCompileExpression(assignment.Value, expectedType, _methodInProgress, builder, this, _diagnostics); if (sourceIndex == -1) { return(false); } // Emit a copy operation builder.AppendInstruction(Opcode.CopyValue, (ushort)sourceIndex, 0, (ushort)targetIndex); return(true); }
/// <summary> /// Fuses two fusible basic blocks. /// </summary> /// <param name="head">The 'head' block.</param> /// <param name="tail">The 'tail' block.</param> private static void FuseBlocks(BasicBlockBuilder head, BasicBlockTag tail) { var jump = (JumpFlow)head.Flow; // Replace branch parameters by their respective arguments. var replacements = new Dictionary <ValueTag, ValueTag>(); foreach (var pair in jump.Branch.ZipArgumentsWithParameters(head.Graph)) { replacements.Add(pair.Key, pair.Value.ValueOrNull); } head.Graph.ReplaceUses(replacements); // Move instructions around. var tailBlock = head.Graph.GetBasicBlock(tail); foreach (var instruction in tailBlock.NamedInstructions) { instruction.MoveTo(head); } // Update the block's flow. head.Flow = tailBlock.Flow; // Delete the 'tail' block. head.Graph.RemoveBasicBlock(tail); }
/// <summary> /// Tries to replace a return flow with a branch to /// the entry point. /// </summary> /// <param name="block"> /// A block that ends in return flow. /// </param> /// <param name="returnValue"> /// The value returned by the return flow. /// </param> /// <param name="graph"> /// The graph that defines the block. /// </param> /// <param name="method"> /// The method that is being optimized. /// </param> /// <param name="ordering"> /// An instruction ordering model. /// </param> /// <returns> /// <c>true</c> if the return has been replaced with a branch /// to the entry point by this method; otherwise, <c>false</c>. /// </returns> private bool TryReplaceWithBranchToEntry( BasicBlockBuilder block, Instruction returnValue, FlowGraphBuilder graph, IMethod method, InstructionOrdering ordering) { var insnsToEliminate = new HashSet <ValueTag>(); ValueTag callTag = null; // Jump through copy instructions. while (returnValue.Prototype is CopyPrototype) { callTag = ((CopyPrototype)returnValue.Prototype).GetCopiedValue(returnValue); insnsToEliminate.Add(callTag); if (!graph.ContainsInstruction(callTag)) { // We encountered a basic block parameter, which we can't really // peek through. Abort. return(false); } returnValue = graph.GetInstruction(callTag).Instruction; } if ((callTag == null || graph.GetValueParent(callTag).Tag == block.Tag) && IsSelfCallPrototype(returnValue.Prototype, method)) { // Now all we have to do is make sure that there is no instruction // that must run after the call. if (callTag != null) { var selection = graph.GetInstruction(callTag).NextInstructionOrNull; while (selection != null) { if (ordering.MustRunBefore(callTag, selection)) { // Aw, snap. Looks like we can't reorder the call, which // means no tail call elimination. Abort. return(false); } selection = selection.NextInstructionOrNull; } } // Remove some instructions. foreach (var tag in insnsToEliminate) { graph.RemoveInstruction(tag); } // Turn the return flow into a jump. block.Flow = new JumpFlow(graph.EntryPointTag, returnValue.Arguments); return(true); } else { return(false); } }
public static ControlFlowGraph CreateGraph(BoundBlock block, FunctionSymbol function = null) { var blockBuilder = new BasicBlockBuilder(); var edgeBuilder = new GraphBuilder(function); var blocks = blockBuilder.Build(block); return(edgeBuilder.Build(blocks)); }
/// <summary> /// Fills the given block. /// </summary> private void FillBlock(BasicBlockBuilder block) { if (!processedBlocks.Add(block.Tag)) { // Block is already being processed. return; } // Try to seal the block right away if possible. if (CanSealBlock(block.Tag)) { SealBlock(block.Tag); } // Actually fill the block, starting with the block's instructions. foreach (var selection in block.NamedInstructions) { Instruction newInstruction; if (RewriteInstruction(selection.Instruction, block, out newInstruction)) { selection.Instruction = newInstruction; } } // Also process instructions in the block's flow. var newFlowInstructions = new List <Instruction>(); bool flowChanged = false; foreach (var instruction in block.Flow.Instructions) { Instruction newInstruction; if (RewriteInstruction(instruction, block, out newInstruction)) { flowChanged = true; newFlowInstructions.Add(newInstruction); } else { newFlowInstructions.Add(instruction); } } if (flowChanged) { block.Flow = block.Flow.WithInstructions(newFlowInstructions); } // Declare the block to be filled. filledBlocks.Add(block.Tag); // Seal this block. SealAllSealableBlocks(); // Fill successor blocks. foreach (var target in block.Flow.BranchTargets) { FillBlock(graphBuilder.GetBasicBlock(target)); } }
public ConditionalAccessOperationTracker( ArrayBuilder <IOperation> operations, BasicBlockBuilder whenNull ) { Debug.Assert(operations != null && whenNull != null); Operations = operations; WhenNull = whenNull; }
public static ControlFlowGraph Create(BoundBlockStatement body) { var basicBlockBuilder = new BasicBlockBuilder(); var blocks = basicBlockBuilder.Build(body); var graphBuilder = new GraphBuilder(); return(graphBuilder.Build(blocks)); }
public void Free() { Ordinal = -1; _statements?.Free(); _statements = null; _predecessors?.Free(); _predecessors = null; _predecessor1 = null; _predecessor2 = null; }
/// <inheritdoc/> public override InstructionBuilder GetInstructionBuilder(BasicBlockBuilder block, int instructionIndex) { if (instructionIndex == 0) { return(new TryFlowInstructionBuilder(block)); } else { throw new IndexOutOfRangeException(); } }
/// <summary> /// Replaces a block's flow with an unconditional jump to an exception /// branch. Replaces 'try' flow exception arguments in that exception /// branch with a captured exception value. /// </summary> /// <param name="block">The block to rewrite.</param> /// <param name="exceptionBranch">The exception branch to jump to unconditionally.</param> /// <param name="capturedException"> /// The captured exception to pass instead of a 'try' flow exception argument. /// </param> private static void JumpToExceptionBranch( BasicBlockBuilder block, Branch exceptionBranch, ValueTag capturedException) { var branch = exceptionBranch.WithArguments( exceptionBranch.Arguments .Select(arg => arg.IsTryException ? BranchArgument.FromValue(capturedException) : arg) .ToArray()); block.Flow = new JumpFlow(branch); }
/// <summary> /// Creates a CIL analysis context. /// </summary> /// <param name="block"> /// The initial basic block to fill. /// </param> /// <param name="analyzer"> /// The method body analyzer to which this /// analysis context belongs. /// </param> /// <param name="exceptionHandlers"> /// The exception handlers for the CIL analysis context. /// </param> public CilAnalysisContext( BasicBlockBuilder block, ClrMethodBodyAnalyzer analyzer, IReadOnlyList <CilExceptionHandler> exceptionHandlers) { this.Block = block; this.Analyzer = analyzer; this.ExceptionHandlers = exceptionHandlers; this.stack = new Stack <ValueTag>( block.Parameters.Select(param => param.Tag)); this.IsTerminated = false; }
private bool TryCompileVariableDeclaration(VariableDeclarationSyntax declaration, BasicBlockBuilder builder) { Debug.Assert(_methodInProgress != null); var localCount = _methodInProgress.Values.Count; // Resolve the type and verify that it is not void if (!TypeResolver.TryResolve(declaration.Type, _diagnostics, out var type)) { return(false); } Debug.Assert(type != null); if (type.Equals(SimpleType.Void)) { _diagnostics.Add(DiagnosticCode.VoidIsNotValidType, declaration.Position, declaration.Name); return(false); } // Compile the initial value (either a runtime expression or compile-time constant) var localIndex = ExpressionCompiler.TryCompileExpression(declaration.InitialValueExpression, type, _methodInProgress, builder, this, _diagnostics); if (localIndex == -1) { return(false); } // There are two cases: // - ExpressionCompiler created temporaries, the last of which is our initialized variable, // - ExpressionCompiler returned a reference to an existent local ("Type value = anotherVariable" only). // In the latter case, we must create a new local and emit a copy. if (localCount == _methodInProgress.Values.Count) { var source = localIndex; // Don't bother figuring out a correct initial value, it won't be used anyways localIndex = _methodInProgress.AddLocal(type, LocalFlags.None); builder.AppendInstruction(Opcode.CopyValue, (ushort)source, 0, (ushort)localIndex); } // Add it to the variable map (unless the name is already in use) if (!_variableMap.TryAddVariable(declaration.Name, localIndex)) { _diagnostics.Add(DiagnosticCode.VariableAlreadyDefined, declaration.Position, declaration.Name); return(false); } return(true); }
public void MoveStatementsFrom(BasicBlockBuilder other) { if (other._statements == null) { return; } else if (_statements == null) { _statements = other._statements; other._statements = null; } else { _statements.AddRange(other._statements); other._statements.Clear(); } }
public void RemovePredecessor(BasicBlockBuilder predecessor) { Debug.Assert(predecessor != null); if (_predecessors != null) { Debug.Assert(_predecessor1 == null); Debug.Assert(_predecessor2 == null); _predecessors.Remove(predecessor); } else if (_predecessor1 == predecessor) { _predecessor1 = null; } else if (_predecessor2 == predecessor) { _predecessor2 = null; } }
private static void ParseInstruction(string line, CompiledMethod method, BasicBlockBuilder builder, Dictionary <string, int> calledMethodIndices) { var lineParts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); Assert.That(Enum.TryParse <Opcode>(lineParts[0], out var opcode), Is.True, $"Unknown opcode: {lineParts[0]}"); if (opcode == Opcode.Return) { // Remove leading # before parsing the value number var sourceIndex = ushort.Parse(lineParts[1].AsSpan(1)); builder.AppendInstruction(Opcode.Return, sourceIndex, 0, 0); } else if (opcode == Opcode.BranchIf) { var sourceIndex = ushort.Parse(lineParts[1].AsSpan(1)); var targetBlockIndex = int.Parse(lineParts[3].AsSpan(3)); builder.AppendInstruction(Opcode.BranchIf, sourceIndex, 0, 0); builder.SetAlternativeSuccessor(targetBlockIndex); } else if (opcode == Opcode.Load) { var value = ResolveValue(lineParts[1]); var destIndex = ushort.Parse(lineParts[3].AsSpan(1)); builder.AppendInstruction(Opcode.Load, value, 0, destIndex); } else if (opcode == Opcode.Call) { // Emulate function indices by creating them sequentially upwards from 100 // If a function is called twice, it will have the same index both times var functionName = lineParts[1].Substring(0, lineParts[1].IndexOf('(')); if (!calledMethodIndices.TryGetValue(functionName, out var functionIndex)) { functionIndex = 100 + calledMethodIndices.Count; calledMethodIndices.Add(functionName, functionIndex); } var destIndex = ushort.Parse(lineParts[^ 1].AsSpan(1));
/// <summary> /// Tries to simplify a basic block's switch flow, provided that the /// block ends in switch flow. /// </summary> /// <param name="block">The basic block to simplify.</param> /// <returns> /// <c>true</c> if the block's flow was simplified; otherwise, <c>false</c>. /// </returns> public static bool TrySimplifySwitchFlow(BasicBlockBuilder block) { if (block.Flow is SwitchFlow) { var flow = (SwitchFlow)block.Flow; var simpleFlow = SimplifySwitchFlow(flow, block.Graph.ImmutableGraph); if (flow != simpleFlow) { if (simpleFlow is JumpFlow) { // If a switch flow gets simplified to a jump // flow, then we should still ensure that the // switch value is evaluated. block.AppendInstruction(flow.SwitchValue); } block.Flow = simpleFlow; return(true); } } return(false); }
/// <inheritdoc/> public override BlockFlow Emit(FlowGraphBuilder graph, ValueTag value) { int caseCounter = 0; BasicBlockBuilder currentBlock = null; var defaultJump = new JumpFlow(flow.DefaultBranch); BlockFlow result = defaultJump; foreach (var switchCase in flow.Cases) { foreach (var pattern in switchCase.Values) { caseCounter++; var nextBlock = graph.AddBasicBlock("case" + caseCounter); var blockFlow = new SwitchFlow( Instruction.CreateCopy(graph.GetValueType(value), value), new[] { new SwitchCase(ImmutableHashSet.Create(pattern), switchCase.Branch) }, new Branch(nextBlock)); if (currentBlock == null) { result = blockFlow; } else { currentBlock.Flow = blockFlow; } currentBlock = nextBlock; } } if (currentBlock != null) { currentBlock.Flow = defaultJump; } return(result); }
/// <summary> /// Emits instructions that write a character to the output stream. /// </summary> /// <param name="block">A basic block builder.</param> /// <param name="character">The character to write to the output stream.</param> public void EmitWrite(ref BasicBlockBuilder block, ValueTag character) { if (WriteMethod == null) { // Do nothing if the 'write' method is null. return; } // Convert the character to a type that the 'write' method is okay with. var convChar = block.AppendInstruction( Instruction.CreateConvertIntrinsic( WriteMethod.Parameters[0].Type, block.Graph.GetValueType(character), character)); // Call the 'write' method. block.AppendInstruction( Instruction.CreateCall( WriteMethod, MethodLookup.Static, new ValueTag[] { convChar })); }
private static ValueTag GetDataPointer( IType elementType, ValueTag cellArray, ValueTag cellIndexAlloca, BasicBlockBuilder block) { var arrayType = block.Graph.GetValueType(cellArray); var indexAllocType = block.Graph.GetValueType(cellIndexAlloca); var indexType = ((PointerType)indexAllocType).ElementType; return(block.AppendInstruction( Instruction.CreateGetElementPointerIntrinsic( elementType, arrayType, new[] { indexType }, cellArray, new ValueTag[] { block.AppendInstruction( Instruction.CreateLoad(indexType, cellIndexAlloca)) }))); }
public void AddPredecessor(BasicBlockBuilder predecessor) { Debug.Assert(predecessor != null); if (_predecessors != null) { Debug.Assert(_predecessor1 == null); Debug.Assert(_predecessor2 == null); _predecessors.Add(predecessor); } else if (_predecessor1 == predecessor) { return; } else if (_predecessor2 == predecessor) { return; } else if (_predecessor1 == null) { _predecessor1 = predecessor; } else if (_predecessor2 == null) { _predecessor2 = predecessor; } else { _predecessors = PooledHashSet <BasicBlockBuilder> .GetInstance(); _predecessors.Add(_predecessor1); _predecessors.Add(_predecessor2); _predecessors.Add(predecessor); Debug.Assert(_predecessors.Count == 3); _predecessor1 = null; _predecessor2 = null; } }
// TODO: Benchmark whether it would be beneficial to pass the unchanging parameters to the private methods // in a readonly struct. We pass a lot of parameters and the call trees run quite deep. /// <summary> /// Compiles the given expression syntax tree and verifies its type. /// Returns the local index if successful, returns -1 and logs diagnostics if the compilation fails. /// </summary> /// <param name="syntax">The root of the expression syntax tree.</param> /// <param name="expectedType">The expected type of the evaluated expression. If null, the type is not checked.</param> /// <param name="method">The method to store and get local values from.</param> /// <param name="builder">Intermediate representation builder.</param> /// <param name="nameResolver">The resolver for variable and method names.</param> /// <param name="diagnostics">Receiver for possible diagnostics.</param> public static int TryCompileExpression( ExpressionSyntax syntax, TypeDefinition?expectedType, CompiledMethod method, BasicBlockBuilder builder, INameResolver nameResolver, IDiagnosticSink diagnostics) { // Compile the expression if (!InternalTryCompileExpression(syntax, method, builder, nameResolver, diagnostics, out var value)) { return(-1); } // Verify the type // TODO: Once multiple integer types exist, there must be some notion of conversions if (expectedType != null && !value.Type.Equals(expectedType)) { diagnostics.Add(DiagnosticCode.TypeMismatch, syntax.Position, value.Type.TypeName, expectedType.TypeName); return(-1); } // Also, if the expression is constant, verify that it is representable if (!value.LocalIndex.HasValue && value.Type.Equals(SimpleType.Int32)) { if (value.ConstantValue.AsSignedInteger < int.MinValue || value.ConstantValue.AsSignedInteger > int.MaxValue) { diagnostics.Add(DiagnosticCode.IntegerConstantOutOfBounds, syntax.Position, value.ConstantValue.ToString(), SimpleType.Int32.TypeName); return(-1); } } // Store the value in a local return(EnsureValueIsStored(value, method, builder)); }
private bool TryCompileWhile(WhileStatementSyntax whileSyntax, BasicBlockBuilder builder, out BasicBlockBuilder newBuilder) { newBuilder = builder; // Default failure case Debug.Assert(_methodInProgress != null); // Create a new basic block which will be the backwards branch target var conditionBuilder = builder.CreateSuccessorBlock(); var conditionBlockIndex = conditionBuilder.Index; // Compile the condition var conditionValue = ExpressionCompiler.TryCompileExpression(whileSyntax.ConditionSyntax, SimpleType.Bool, _methodInProgress, conditionBuilder, this, _diagnostics); if (conditionValue == -1) { return(false); } // Then compile the body. // The return guarantee is not propagated up as we don't know whether the loop will ever be entered. // TODO: Recognizing compile-time constant condition var bodyBuilder = conditionBuilder.CreateBranch((ushort)conditionValue); if (!TryCompileBlock(whileSyntax.BodySyntax, bodyBuilder, out bodyBuilder, out var _)) { return(false); } // Create the backwards branch bodyBuilder.SetSuccessor(conditionBlockIndex); // Create the exit branch newBuilder = conditionBuilder.CreateSuccessorBlock(); return(true); }
private bool TryCompileReturn(ReturnStatementSyntax syntax, BasicBlockBuilder builder) { Debug.Assert(_methodInProgress != null); Debug.Assert(_declaration != null); var returnValueNumber = -1; // ExpressionCompiler returns -1 on failure if (syntax.ResultExpression is null) { // Void return: verify that the method really returns void, then add a void local to return if (_declaration.ReturnType.Equals(SimpleType.Void)) { returnValueNumber = _methodInProgress.AddLocal(SimpleType.Void, LocalFlags.None); } else { _diagnostics.Add(DiagnosticCode.TypeMismatch, syntax.Position, SimpleType.Void.TypeName, _declaration.ReturnType.TypeName); } } else { // Non-void return: parse the expression, verifying the type returnValueNumber = ExpressionCompiler.TryCompileExpression(syntax.ResultExpression, _declaration.ReturnType, _methodInProgress, builder, this, _diagnostics); } // At this point, a diagnostic should already be logged if (returnValueNumber == -1) { return(false); } builder.AppendInstruction(Opcode.Return, (ushort)returnValueNumber, 0, 0); return(true); }
public ImmutableArray <ControlFlowBranch> ConvertPredecessorsToBranches(ArrayBuilder <BasicBlock> blocks) { if (!HasPredecessors) { _predecessors?.Free(); _predecessors = null; return(ImmutableArray <ControlFlowBranch> .Empty); } BasicBlock block = blocks[Ordinal]; var branches = ArrayBuilder <ControlFlowBranch> .GetInstance(_predecessors?.Count ?? 2); if (_predecessors != null) { Debug.Assert(_predecessor1 == null); Debug.Assert(_predecessor2 == null); foreach (BasicBlockBuilder predecessorBlockBuilder in _predecessors) { addBranches(predecessorBlockBuilder); } _predecessors.Free(); _predecessors = null; } else { if (_predecessor1 != null) { addBranches(_predecessor1); _predecessor1 = null; } if (_predecessor2 != null) { addBranches(_predecessor2); _predecessor2 = null; } } // Order predecessors by source ordinal and conditional first to ensure deterministic predecessor ordering. branches.Sort((x, y) => { int result = x.Source.Ordinal - y.Source.Ordinal; if (result == 0 && x.IsConditionalSuccessor != y.IsConditionalSuccessor) { if (x.IsConditionalSuccessor) { result = -1; } else { result = 1; } } return(result); }); return(branches.ToImmutableAndFree()); void addBranches(BasicBlockBuilder predecessorBlockBuilder) { BasicBlock predecessor = blocks[predecessorBlockBuilder.Ordinal]; if (predecessor.FallThroughSuccessor.Destination == block) { branches.Add(predecessor.FallThroughSuccessor); } if (predecessor.ConditionalSuccessor?.Destination == block) { branches.Add(predecessor.ConditionalSuccessor); } } }
/// <summary> /// Generates code that runs the given type's static constructors. /// </summary> /// <param name="BasicBlock">The basic block to extend.</param> /// <param name="Type">The type whose static constructors are to be run.</param> /// <returns>A basic block builder.</returns> public BasicBlockBuilder EmitRunStaticConstructors(BasicBlockBuilder BasicBlock, LLVMType Type) { if (Type.StaticConstructors.Count == 0) { return(BasicBlock); } // Get or create the lock triple for the type. A lock triple consists of: // // * A global Boolean flag that tells if all static constructors for // the type have been run. // // * A global Boolean flag that tells if the static constructors for // the type are in the process of being run. // // * A thread-local Boolean flag that tells if the static constructors // for the type are in the process of being run **by the current // thread.** // // * A common metadata node per thread-local variable that is used to // group loads/stores to that variable in an '!invariant.group'. // Tuple <LLVMValueRef, LLVMValueRef, LLVMValueRef, LLVMValueRef> lockQuad; if (!staticConstructorLocks.TryGetValue(Type, out lockQuad)) { string typeName = Type.Namespace.Assembly.Abi.Mangler.Mangle(Type, true); var hasRunGlobal = AddGlobal(module, Int8Type(), typeName + "__has_run_cctor"); hasRunGlobal.SetInitializer(ConstInt(Int8Type(), 0, false)); hasRunGlobal.SetLinkage(LLVMLinkage.LLVMInternalLinkage); var isRunningGlobal = AddGlobal(module, Int8Type(), typeName + "__is_running_cctor"); isRunningGlobal.SetInitializer(ConstInt(Int8Type(), 0, false)); isRunningGlobal.SetLinkage(LLVMLinkage.LLVMInternalLinkage); var isRunningHereGlobal = AddGlobal(module, Int1Type(), typeName + "__is_running_cctor_here"); isRunningHereGlobal.SetInitializer(ConstInt(Int1Type(), 0, false)); isRunningHereGlobal.SetLinkage(LLVMLinkage.LLVMInternalLinkage); isRunningHereGlobal.SetThreadLocal(true); var mdNode = MDNode(new LLVMValueRef[] { MDString(typeName + "__is_running_cctor_here") }); lockQuad = new Tuple <LLVMValueRef, LLVMValueRef, LLVMValueRef, LLVMValueRef>( hasRunGlobal, isRunningGlobal, isRunningHereGlobal, mdNode); staticConstructorLocks[Type] = lockQuad; } // A type's static constructors need to be run *before* any of its static fields // are accessed. Also, we need to ensure that a type's static constructors are // run only *once* in both single-threaded and multi-threaded environments. // // We'll generate something along the lines of this when a type's static // constructors need to be run: // // if (!is_running_cctor_here && !has_run_cctor) // { // while (!Interlocked.CompareExchange(ref is_running_cctor, false, true)) { } // if (!has_run_cctor) // { // cctor(); // has_run_cctor = true; // } // is_running_cctors = false; // } // // Or, using gotos: // // if (is_running_cctors_here) goto after_cctor; // else goto check_has_run_cctor; // // check_has_run_cctor: // is_running_cctors_here = true; // if (has_run_cctor) goto after_cctor; // else goto wait_for_cctor_lock; // // wait_for_cctor_lock: // has_exchanged = Interlocked.CompareExchange(ref is_running_cctos, false, true); // if (has_exchanged) goto maybe_run_cctor; // else goto wait_for_cctor_lock; // // maybe_run_cctor: // if (has_run_cctor) goto reset_lock; // else goto run_cctor; // // run_cctor: // cctor(); // has_run_cctor = true; // goto reset_lock; // // reset_lock: // is_running_cctors = false; // goto after_cctor; // // after_cctor: // ... var hasRunPtr = lockQuad.Item1; var isRunningPtr = lockQuad.Item2; var isRunningHerePtr = lockQuad.Item3; var invariantGroup = lockQuad.Item4; var invariantGroupMdId = GetMDKindID("invariant.group"); var checkHasRunBlock = BasicBlock.CreateChildBlock("check_has_run_cctor"); var waitForLockBlock = BasicBlock.CreateChildBlock("wait_for_cctor_lock"); var maybeRunBlock = BasicBlock.CreateChildBlock("maybe_run_cctor"); var runBlock = BasicBlock.CreateChildBlock("run_cctor"); var resetLockBlock = BasicBlock.CreateChildBlock("reset_lock"); var afterBlock = BasicBlock.CreateChildBlock("after_cctor"); // Implement the entry basic block. BuildCondBr( BasicBlock.Builder, WithMetadata( BuildLoad(BasicBlock.Builder, isRunningHerePtr, "is_running_cctor_here"), invariantGroupMdId, invariantGroup), afterBlock.Block, checkHasRunBlock.Block); // Implement the `check_has_run_cctor` block. WithMetadata( BuildStore(checkHasRunBlock.Builder, ConstInt(Int1Type(), 1, false), isRunningHerePtr), invariantGroupMdId, invariantGroup); BuildCondBr( checkHasRunBlock.Builder, IntToBoolean( checkHasRunBlock.Builder, BuildAtomicLoad(checkHasRunBlock.Builder, hasRunPtr, "has_run_cctor")), afterBlock.Block, waitForLockBlock.Block); // Implement the `wait_for_cctor_lock` block. var cmpxhg = BuildAtomicCmpXchg( waitForLockBlock.Builder, isRunningPtr, ConstInt(Int8Type(), 0, false), ConstInt(Int8Type(), 1, false), LLVMAtomicOrdering.LLVMAtomicOrderingSequentiallyConsistent, LLVMAtomicOrdering.LLVMAtomicOrderingMonotonic, false); cmpxhg.SetValueName("is_running_cctor_cmpxhg"); BuildCondBr( waitForLockBlock.Builder, BuildExtractValue( waitForLockBlock.Builder, cmpxhg, 1, "has_exchanged"), maybeRunBlock.Block, waitForLockBlock.Block); // Implement the `maybe_run_cctor` block. BuildCondBr( maybeRunBlock.Builder, IntToBoolean( maybeRunBlock.Builder, BuildAtomicLoad(maybeRunBlock.Builder, hasRunPtr, "has_run_cctor")), resetLockBlock.Block, runBlock.Block); // Implement the `run_cctor` block. for (int i = 0; i < Type.StaticConstructors.Count; i++) { BuildCall(runBlock.Builder, Declare(Type.StaticConstructors[i]), new LLVMValueRef[] { }, ""); } BuildAtomicStore(runBlock.Builder, ConstInt(Int8Type(), 1, false), hasRunPtr); BuildBr(runBlock.Builder, resetLockBlock.Block); // Implement the `reset_lock` block. BuildAtomicStore(resetLockBlock.Builder, ConstInt(Int8Type(), 0, false), isRunningPtr); BuildBr(resetLockBlock.Builder, afterBlock.Block); return(afterBlock); }
private void ThreadJumps(BasicBlockBuilder block, HashSet <BasicBlockTag> processedBlocks) { if (!processedBlocks.Add(block)) { return; } var flow = block.Flow; if (flow is JumpFlow) { // Jump flow is usually fairly straightforward to thread. var jumpFlow = (JumpFlow)flow; var threadFlow = AsThreadableFlow(jumpFlow.Branch, block.Graph, processedBlocks); if (threadFlow != null && jumpFlow.Branch.Target != block.Tag) { block.Flow = threadFlow; if (block.Flow is SwitchFlow) { // If the jump flow gets replaced by switch flow, then we want to // jump-thread the block again. processedBlocks.Remove(block); ThreadJumps(block, processedBlocks); return; } } // Now would also be a good time to try and fuse this block with // its successor, if this jump is the only branch to the successor. var predecessors = block.Graph.GetAnalysisResult <BasicBlockPredecessors>(); BasicBlockTag tail; if (BlockFusion.TryGetFusibleTail(block, block.Graph.ToImmutable(), predecessors, out tail)) { // Fuse the blocks. FuseBlocks(block, tail); // Fusing these blocks may have exposed additional information // that will enable us to thread more jumps. processedBlocks.Remove(block); ThreadJumps(block, processedBlocks); } } else if (flow is TryFlow) { // We might be able to turn try flow into a jump, which we can thread // recursively. var tryFlow = (TryFlow)flow; // Start off by threading the try flow's branches. var successBranch = tryFlow.SuccessBranch; var failureBranch = tryFlow.ExceptionBranch; var successFlow = AsThreadableFlow(tryFlow.SuccessBranch, block.Graph, processedBlocks); var failureFlow = AsThreadableFlow(tryFlow.ExceptionBranch, block.Graph, processedBlocks); if (successFlow is JumpFlow) { successBranch = ((JumpFlow)successFlow).Branch; } if (failureFlow is JumpFlow) { failureBranch = ((JumpFlow)failureFlow).Branch; } var exceptionSpecs = block.Graph.GetAnalysisResult <InstructionExceptionSpecs>(); if (!exceptionSpecs.GetExceptionSpecification(tryFlow.Instruction).CanThrowSomething || IsRethrowBranch(failureBranch, block.Graph, processedBlocks)) { // If the "risky" instruction absolutely cannot throw, then we can // just rewrite the try as a jump. // Similarly, if the exception branch simply re-throws the exception, // then we are also free to make the same transformation, but for // different reasons. var value = block.AppendInstruction(tryFlow.Instruction); var branch = successBranch.WithArguments( successBranch.Arguments .Select(arg => arg.IsTryResult ? BranchArgument.FromValue(value) : arg) .ToArray()); block.Flow = new JumpFlow(branch); // Jump-thread this block again. processedBlocks.Remove(block); ThreadJumps(block, processedBlocks); } else if (IsRethrowIntrinsic(tryFlow.Instruction)) { // If the "risky" instruction is really just a 'rethrow' intrinsic, // then we can branch directly to the exception branch. var capturedException = tryFlow.Instruction.Arguments[0]; JumpToExceptionBranch(block, failureBranch, capturedException); // Jump-thread this block again. processedBlocks.Remove(block); ThreadJumps(block, processedBlocks); } else if (IsThrowIntrinsic(tryFlow.Instruction)) { // If the "risky" instruction is really just a 'throw' intrinsic, // then we can insert an instruction to capture the exception and // jump directly to the exception block. var exception = tryFlow.Instruction.Arguments[0]; var capturedExceptionParam = failureBranch .ZipArgumentsWithParameters(block.Graph) .FirstOrDefault(pair => pair.Value.IsTryException) .Key; if (capturedExceptionParam == null) { // If the exception branch does not pass an '#exception' parameter, // then we can replace the try by a simple jump. block.Flow = new JumpFlow(failureBranch); } else { // Otherwise, we actually need to capture the exception. var capturedException = block.AppendInstruction( Instruction.CreateCaptureIntrinsic( block.Graph.GetValueType(capturedExceptionParam), block.Graph.GetValueType(exception), exception)); JumpToExceptionBranch(block, failureBranch, capturedException); } // Jump-thread this block again. processedBlocks.Remove(block); ThreadJumps(block, processedBlocks); } else { // If we can't replace the 'try' flow with something better, then // the least we can do is try and thread the 'try' flow's branches. block.Flow = new TryFlow(tryFlow.Instruction, successBranch, failureBranch); } } else if (flow is SwitchFlow && IncludeSwitches) { // If we're allowed to, then we might be able to thread jumps in switch flow // branches. bool changed = false; var switchFlow = (SwitchFlow)flow; // Rewrite switch cases. var cases = new List <SwitchCase>(); foreach (var switchCase in switchFlow.Cases) { var caseFlow = AsThreadableFlow(switchCase.Branch, block.Graph, processedBlocks); if (caseFlow != null && switchCase.Branch.Target != block.Tag) { if (caseFlow is JumpFlow) { cases.Add( new SwitchCase( switchCase.Values, ((JumpFlow)caseFlow).Branch)); changed = true; continue; } else if (caseFlow is SwitchFlow) { var threadedSwitch = (SwitchFlow)caseFlow; if (threadedSwitch.SwitchValue == switchFlow.SwitchValue) { var valuesToBranches = threadedSwitch.ValueToBranchMap; foreach (var value in switchCase.Values) { Branch branchForValue; if (!valuesToBranches.TryGetValue(value, out branchForValue)) { branchForValue = threadedSwitch.DefaultBranch; } cases.Add( new SwitchCase( ImmutableHashSet.Create(value), branchForValue)); } changed = true; continue; } } } cases.Add(switchCase); } // Rewrite default branch if possible. var defaultBranch = switchFlow.DefaultBranch; if (defaultBranch.Target != block.Tag) { var defaultFlow = AsThreadableFlow(defaultBranch, block.Graph, processedBlocks); if (defaultFlow is JumpFlow) { defaultBranch = ((JumpFlow)defaultFlow).Branch; changed = true; } else if (defaultFlow is SwitchFlow) { var threadedSwitch = (SwitchFlow)defaultFlow; if (threadedSwitch.SwitchValue == switchFlow.SwitchValue) { var valueSet = ImmutableHashSet.CreateRange(switchFlow.ValueToBranchMap.Keys); foreach (var switchCase in threadedSwitch.Cases) { cases.Add( new SwitchCase( switchCase.Values.Except(valueSet), switchCase.Branch)); } defaultBranch = threadedSwitch.DefaultBranch; changed = true; } } } if (changed) { block.Flow = new SwitchFlow( switchFlow.SwitchValue, cases, defaultBranch); } // Also simplify the block's switch flow. If the switch // flow decays to jump flow, then we want to try threading // this block again. if (SwitchSimplification.TrySimplifySwitchFlow(block) && block.Flow is JumpFlow) { processedBlocks.Remove(block); ThreadJumps(block, processedBlocks); } } }
private static bool InternalTryCompileExpression( ExpressionSyntax syntax, CompiledMethod method, BasicBlockBuilder builder, INameResolver nameResolver, IDiagnosticSink diagnostics, out Temporary value) { value = default; if (syntax is IntegerLiteralSyntax integer) { // Decide the type for the literal // TODO: There is for now only support for int32 and uint32, and only because the smallest int32 should be // TODO: representable. This logic should be updated once multiple integer types officially exist. if (integer.Value > uint.MaxValue) { diagnostics.Add(DiagnosticCode.IntegerConstantOutOfBounds, syntax.Position, integer.Value.ToString(), SimpleType.Int32.TypeName); return(false); } else if (integer.Value > int.MaxValue) { // TODO: This does not work for anything else than int32 minimum value = Temporary.FromConstant(SimpleType.UInt32, ConstantValue.SignedInteger((long)integer.Value)); return(true); } else { value = Temporary.FromConstant(SimpleType.Int32, ConstantValue.SignedInteger((long)integer.Value)); return(true); } } else if (syntax is BooleanLiteralSyntax boolean) { value = Temporary.FromConstant(SimpleType.Bool, ConstantValue.Bool(boolean.Value)); return(true); } else if (syntax is IdentifierSyntax named) { if (!nameResolver.TryResolveVariable(named.Name, out var valueNumber)) { diagnostics.Add(DiagnosticCode.VariableNotFound, named.Position, named.Name); return(false); } value = Temporary.FromLocal(method.Values[valueNumber].Type, (ushort)valueNumber); return(true); } else if (syntax is UnaryExpressionSyntax unary) { if (!InternalTryCompileExpression(unary.InnerExpression, method, builder, nameResolver, diagnostics, out var inner)) { return(false); } return(TryCompileUnary(unary, inner, method, builder, diagnostics, out value)); } else if (syntax is BinaryExpressionSyntax binary) { if (!InternalTryCompileExpression(binary.Left, method, builder, nameResolver, diagnostics, out var left) || !InternalTryCompileExpression(binary.Right, method, builder, nameResolver, diagnostics, out var right)) { return(false); } return(TryCompileBinary(binary, left, right, method, builder, diagnostics, out value)); } else if (syntax is FunctionCallSyntax call) { return(TryCompileCall(call, method, builder, diagnostics, nameResolver, out value)); } else { throw new NotImplementedException(); } }
/// <inheritdoc/> public override InstructionBuilder GetInstructionBuilder(BasicBlockBuilder block, int instructionIndex) { throw new IndexOutOfRangeException(); }
private static bool TryCompileCall(FunctionCallSyntax call, CompiledMethod method, BasicBlockBuilder builder, IDiagnosticSink diagnostics, INameResolver nameResolver, out Temporary value) { value = default; // Get the callee var matchingMethods = nameResolver.ResolveMethod(call.Function.Name); if (matchingMethods.Count == 0) { diagnostics.Add(DiagnosticCode.MethodNotFound, call.Function.Position, call.Function.Name); return(false); } else if (matchingMethods.Count > 1) { // TODO: Test this case throw new NotImplementedException("Multiple matching methods"); } var declaration = matchingMethods[0]; // Assert that there is the right number of parameters if (call.Parameters.Count != declaration.ParameterTypes.Count) { diagnostics.Add(DiagnosticCode.ParameterCountMismatch, call.Position, actual: call.Parameters.Count.ToString(), expected: declaration.ParameterTypes.Count.ToString()); return(false); } // Evaluate the parameters, verifying their types var parameterIndices = new int[declaration.ParameterTypes.Count]; for (var i = 0; i < parameterIndices.Length; i++) { var paramIndex = TryCompileExpression(call.Parameters[i], declaration.ParameterTypes[i], method, builder, nameResolver, diagnostics); // If the compilation of the expression failed for some reason, the diagnostic is already logged if (paramIndex == -1) { return(false); } parameterIndices[i] = paramIndex; } // Emit a call operation var callType = declaration is ImportedMethodDeclaration ? MethodCallType.Imported : MethodCallType.Native; var callInfoIndex = method.AddCallInfo(declaration.BodyIndex, parameterIndices, declaration.FullName, callType); var resultIndex = method.AddLocal(declaration.ReturnType, LocalFlags.None); builder.AppendInstruction(Opcode.Call, callInfoIndex, 0, resultIndex); value = Temporary.FromLocal(declaration.ReturnType, resultIndex); return(true); }