public void WriteToFileTest( ) { string path = Path.GetTempFileName( ); try { using (var context = new Context( )) using (var module = context.CreateBitcodeModule(TestModuleName)) { /*Function testFunc =*/ CreateSimpleVoidNopTestFunction(module, "foo"); module.WriteToFile(path); } using (var ctx = new Context( )) using (var module2 = BitcodeModule.LoadFrom(path, ctx)) { // force a GC to ensure buffer created in LoadFrom is handled correctly GC.Collect(GC.MaxGeneration); IrFunction testFunc = module2.GetFunction("foo"); // verify basics Assert.IsNotNull(testFunc); string txt = module2.WriteToString( ); Assert.IsFalse(string.IsNullOrWhiteSpace(txt)); string expectedText = string.Format(CultureInfo.InvariantCulture, TestModuleTemplate, Environment.NewLine, path); Assert.AreEqual(expectedText, txt); } } finally { File.Delete(path); } }
private void AddDebugInfoForAlloca(Alloca argSlot, IrFunction function, LocalVariableDeclaration localVar) { uint line = ( uint )localVar.Location.StartLine; uint col = ( uint )localVar.Location.StartColumn; // Keep compiler happy on null checks by asserting on expectations // The items were created in this file with all necessary info so // these properties should never be null. Debug.Assert(function.DISubProgram != null, "expected function with non-null DISubProgram"); Debug.Assert(function.DISubProgram.File != null, "expected function with non-null DISubProgram.File"); Debug.Assert(InstructionBuilder.InsertBlock != null, "expected Instruction builder with non-null insertion block"); DILocalVariable debugVar = Module.DIBuilder.CreateLocalVariable(scope: function.DISubProgram , name: localVar.Name , file: function.DISubProgram.File , line , type: DoubleType , alwaysPreserve: false , debugFlags: DebugInfoFlags.None ); Module.DIBuilder.InsertDeclare(storage: argSlot , varInfo: debugVar , location: new DILocation(Context, line, col, function.DISubProgram) , insertAtEnd: InstructionBuilder.InsertBlock ); }
public void AddABIAttributesForByValueStructure(IrFunction function, int paramIndex) { if (!(function.Parameters[paramIndex].NativeType is IPointerType argType) || !argType.ElementType.IsStruct) { throw new ArgumentException("Signature for specified parameter must be a pointer to a structure"); } }
private static BitcodeModule CreateModule(Context ctx, TargetMachine machine, int magicNumber, string modulename = "test", string functionname = "main") { var module = ctx.CreateBitcodeModule(modulename); module.Layout = machine.TargetData; IrFunction main = module.CreateFunction(functionname, ctx.GetFunctionType(ctx.Int32Type)); BasicBlock entryBlock = main.AppendBasicBlock("entry"); var bldr = new InstructionBuilder(entryBlock); bldr.Return(ctx.CreateConstant(magicNumber)); return(module); }
public void VerifyInvalidModuleTest( ) { using var context = new Context( ); using var module = context.CreateBitcodeModule(TestModuleName); IrFunction testFunc = CreateInvalidFunction(module, "badfunc"); // verify basics Assert.IsNotNull(testFunc); bool isValid = module.Verify(out string msg); Assert.IsFalse(isValid); Assert.IsNotNull(msg); }
public void VerifyValidModuleTest( ) { using var context = new Context( ); using var module = context.CreateBitcodeModule(TestModuleName); IrFunction testFunc = CreateSimpleVoidNopTestFunction(module, "foo"); // verify basics Assert.IsNotNull(testFunc); bool isValid = module.Verify(out string msg); Assert.IsTrue(isValid); Assert.AreEqual(string.Empty, msg); }
private static void CreateDoCopyFunctionBody(BitcodeModule module , IrFunction doCopyFunc , IStructType foo , GlobalVariable bar , GlobalVariable baz , IrFunction copyFunc ) { var bytePtrType = module.Context.Int8Type.CreatePointerType( ); // create block for the function body, only need one for this simple sample var blk = doCopyFunc.AppendBasicBlock("entry"); Debug.Assert(doCopyFunc.DISubProgram != null, "Expected non null subProgram"); // create instruction builder to build the body var instBuilder = new InstructionBuilder(blk); bool param0ByVal = copyFunc.Attributes[FunctionAttributeIndex.Parameter0].Contains(AttributeKind.ByVal); if (!param0ByVal) { // create a temp local copy of the global structure var dstAddr = instBuilder.Alloca(foo) .RegisterName("agg.tmp") .Alignment(module.Layout.CallFrameAlignmentOf(foo)); instBuilder.SetDebugLocation(25, 11, doCopyFunc.DISubProgram); var bitCastDst = instBuilder.BitCast(dstAddr, bytePtrType); var bitCastSrc = instBuilder.BitCast(bar, bytePtrType); instBuilder.MemCpy(bitCastDst , bitCastSrc , module.Context.CreateConstant(module.Layout.ByteSizeOf(foo)) , false ); instBuilder.SetDebugLocation(25, 5, doCopyFunc.DISubProgram) .Call(copyFunc, dstAddr, baz); } else { instBuilder.SetDebugLocation(25, 5, doCopyFunc.DISubProgram) .Call(copyFunc, bar, baz) .AddAttributes(FunctionAttributeIndex.Parameter0, copyFunc.Parameters[0].Attributes); } instBuilder.SetDebugLocation(26, 1, doCopyFunc.DISubProgram) .Return( ); }
public void AddFunctionGetFunctionTest( ) { using var context = new Context( ); using var module = context.CreateBitcodeModule(TestModuleName); IrFunction testFunc = CreateSimpleVoidNopTestFunction(module, "foo"); // verify basics Assert.IsNotNull(testFunc); Assert.AreSame(module, testFunc.ParentModule); Assert.AreEqual("foo", testFunc.Name); // Verify the function is in the module, and getting it retrieves the same instance Assert.IsTrue(module.TryGetFunction("foo", out IrFunction? funcFromModule)); Assert.AreSame(testFunc, funcFromModule); }
public void AsStringTest( ) { using (var context = new Context( )) using (var module = context.CreateBitcodeModule(TestModuleName)) { IrFunction testFunc = CreateSimpleVoidNopTestFunction(module, "foo"); // verify basics Assert.IsNotNull(testFunc); string txt = module.WriteToString( ); Assert.IsFalse(string.IsNullOrWhiteSpace(txt)); string expectedText = string.Format(CultureInfo.InvariantCulture, TestModuleTemplate, Environment.NewLine, "test"); Assert.AreEqual(expectedText, txt); } }
/// <summary>Looks up a function in the module by name</summary> /// <param name="name">Name of the function</param> /// <param name="function">The function or <see langword="default"/> if not found</param> /// <returns><see langword="true"/> if the function was found or <see langword="false"/> if not</returns> public bool TryGetFunction(string name, out IrFunction function) { ThrowIfDisposed( ); var funcRef = ModuleHandle.GetNamedFunction(name); if (funcRef == default) { function = default; return(false); } function = Value.FromHandle <IrFunction>(funcRef) !; return(true); }
public void AddFunctionGetFunctionTest( ) { using (var context = new Context( )) using (var module = context.CreateBitcodeModule(TestModuleName)) { IrFunction testFunc = CreateSimpleVoidNopTestFunction(module, "foo"); // verify basics Assert.IsNotNull(testFunc); Assert.AreSame(module, testFunc.ParentModule); Assert.AreEqual("foo", testFunc.Name); // Verify the function is in the module var funcFromModule = module.GetFunction("foo"); Assert.AreSame(testFunc, funcFromModule); } }
public void VerifyInvalidModuleTest( ) { using (var context = new Context( )) using (var module = context.CreateBitcodeModule(TestModuleName)) { IrFunction testFunc = CreateInvalidFunction(module, "badfunc"); // verify basics Assert.IsNotNull(testFunc); bool isValid = module.Verify(out string msg); Assert.IsFalse(isValid); Assert.IsNotNull(msg); /* while verifying the contents of the message might be a normal test * it comes from the underlying wrapped LLVM and is subject to change * by the LLVM team and is therefore outside the control of LLVM.NET */ } }
private void AddDebugInfoForAlloca(Alloca argSlot, IrFunction function, LocalVariableDeclaration localVar) { uint line = ( uint )localVar.Location.StartLine; uint col = ( uint )localVar.Location.StartColumn; DILocalVariable debugVar = Module.DIBuilder.CreateLocalVariable(scope: function.DISubProgram , name: localVar.Name , file: function.DISubProgram.File , line , type: DoubleType , alwaysPreserve: false , debugFlags: DebugInfoFlags.None ); Module.DIBuilder.InsertDeclare(storage: argSlot , varInfo: debugVar , location: new DILocation(Context, line, col, function.DISubProgram) , insertAtEnd: InstructionBuilder.InsertBlock ); }
private void AddDebugInfoForAlloca(Alloca argSlot, IrFunction function, ParameterDeclaration param) { uint line = ( uint )param.Location.StartLine; uint col = ( uint )param.Location.StartColumn; DILocalVariable debugVar = Module.DIBuilder.CreateArgument(scope: function.DISubProgram , name: param.Name , file: function.DISubProgram.File , line , type: DoubleType , alwaysPreserve: true , debugFlags: DebugInfoFlags.None , argNo: checked (( ushort )(param.Index + 1)) // Debug index starts at 1! ); Module.DIBuilder.InsertDeclare(storage: argSlot , varInfo: debugVar , location: new DILocation(Context, line, col, function.DISubProgram) , insertAtEnd: InstructionBuilder.InsertBlock ); }
public void AddAliasGetAliasTest( ) { using var context = new Context( ); using var module = context.CreateBitcodeModule(TestModuleName); IrFunction testFunc = CreateSimpleVoidNopTestFunction(module, "_test"); var alias = module.AddAlias(testFunc, TestModuleName); Assert.AreSame(alias, module.GetAlias(TestModuleName)); Assert.AreSame(module, alias.ParentModule); Assert.AreSame(testFunc, alias.Aliasee); Assert.AreEqual(TestModuleName, alias.Name); Assert.AreEqual(Linkage.External, alias.Linkage); Assert.AreSame(testFunc.NativeType, alias.NativeType); Assert.AreEqual(1, alias.Operands.Count); Assert.AreSame(testFunc, alias.Aliasee); Assert.IsFalse(alias.IsNull); Assert.IsFalse(alias.IsUndefined); Assert.IsFalse(alias.IsZeroValue); }
public void AddABIAttributesForByValueStructure(IrFunction function, int paramIndex) { // ByVal pointers indicate by value semantics. The actual LLVM semantics are along the lines of // "pass the arg as copy on the arguments stack and set parameter implicitly to that copy's address" // (src: https://github.com/ldc-developers/ldc/issues/937 ) [e.g. caller copies byval args] // // LLVM recognizes this pattern and has a pass to map to an efficient register usage whenever plausible. // Though it seems Clang doesn't apply the attribute in all cases, for x86 it doesn't appear to ever use // it, for Cortex-Mx it seems to use it only for larger structs, otherwise it uses an [ n x i32]. (Max // value of n is not known) and performs casts. Thus, on cortex-m the function parameters are handled // quite differently by clang, which seems odd to put such target dependent differences into the front-end. if (!(function.Parameters[paramIndex].NativeType is IPointerType argType) || !argType.ElementType.IsStruct) { throw new ArgumentException("Signature for specified parameter must be a pointer to a structure"); } var layout = function.ParentModule.Layout; function.AddAttributes(FunctionAttributeIndex.Parameter0 + paramIndex , function.Context.CreateAttribute(AttributeKind.ByVal) , function.Context.CreateAttribute(AttributeKind.Alignment, layout.AbiAlignmentOf(argType.ElementType)) ); }
void Compile(IrFunction func) { Write($"public static {From(func.ReturnType)} {func.Name}({string.Join(", ", func.ParameterTypes.Select((pt, i) => $"{From(pt)} _{i}"))}) {{"); Indentation++; var blockPhiResolvers = new Dictionary <string, List <string> >(); foreach (var block in func.Blocks) { blockPhiResolvers[block.Name] = new List <string>(); } foreach (var block in func.Blocks) { foreach (var inst in block.Instructions) { if (inst is PhiInst phi) { Write($"{From(phi.Output.Type)} {Rewrite(phi.Output)};"); foreach (var(bname, value) in phi.Incoming) { blockPhiResolvers[bname].Add($"{Rewrite(phi.Output)} = {Rewrite(value)};"); } } } } foreach (var block in func.Blocks) { bool finished = false; void FinishBlock() { if (finished) { return; } finished = true; blockPhiResolvers[block.Name].ForEach(Write); } Write($"{RenameBlock(block.Name)}:"); Indentation++; foreach (var inst in block.Instructions) { switch (inst) { case AllocaInst a: Write($"var {Rewrite(a.Output)} = stackalloc {From(a.AllocationType)}[{Rewrite(a.AllocationRank)}];"); break; case BinaryInst b: Write($"var {Rewrite(b.Output)} = {Rewrite(b.A)} {BinaryOps[b.Op]} {Rewrite(b.B)};"); break; case BitcastInst bc: Write($"var {Rewrite(bc.Output)} = ({From(bc.Output.Type)}) {Rewrite(bc.Value)};"); break; case BrIfInst bri: FinishBlock(); Write($"if({Rewrite(bri.Condition)}) goto {RenameBlock(bri.If)};"); Write($"else goto {RenameBlock(bri.Else)};"); break; case BrInst br: FinishBlock(); Write($"goto {RenameBlock(br.Target)};"); break; case CallInst call: Write($"var {Rewrite(call.Output)} = {call.Target}({string.Join(", ", call.Parameters.Select(Rewrite))});"); break; case IcmpInst icmp: Write($"var {Rewrite(icmp.Output)} = {Rewrite(icmp.A)} {CompareOps[icmp.Predicate]} {Rewrite(icmp.B)};"); break; case LoadInst load: Write($"var {Rewrite(load.Output)} = *{Rewrite(load.Pointer)};"); break; case PhiInst phi: break; case ReturnInst ret: FinishBlock(); Write(ret.Value == null ? "return;" : $"return {Rewrite(ret.Value)};"); break; case SelectInst select: Write($"var {Rewrite(select.Output)} = {Rewrite(select.Compare)} ? {Rewrite(select.A)} : {Rewrite(select.B)};"); break; case StoreInst store: Write($"*{Rewrite(store.Pointer)} = {Rewrite(store.Value)};"); break; default: Console.Write("Unknown instruction: "); inst.Print(); break; } } FinishBlock(); Indentation--; } Indentation--; Write("}"); }
public static void Main(string[] args) { #region CommandlineArguments if (args.Length < 2 || args.Length > 3) { ShowUsage( ); return; } string outputPath = args.Length == 3 ? args[2] : Environment.CurrentDirectory; string srcPath = args[1]; if (!File.Exists(srcPath)) { Console.Error.WriteLine("Src file not found: '{0}'", srcPath); return; } srcPath = Path.GetFullPath(srcPath); #endregion using var libLLVM = InitializeLLVM( ); #region TargetDetailsSelection switch (args[0].ToUpperInvariant( )) { case "M3": TargetDetails = new CortexM3Details(libLLVM); break; case "X64": TargetDetails = new X64Details(libLLVM); break; default: ShowUsage( ); return; } string moduleName = $"test_{TargetDetails.ShortName}.bc"; #endregion #region CreatingModule using var context = new Context( ); using var module = context.CreateBitcodeModule(moduleName, SourceLanguage.C99, srcPath, VersionIdentString); module.SourceFileName = Path.GetFileName(srcPath); module.TargetTriple = TargetDetails.TargetMachine.Triple; module.Layout = TargetDetails.TargetMachine.TargetData; Debug.Assert(!(module.DICompileUnit is null), "Expected module with non-null compile unit"); TargetDependentAttributes = TargetDetails.BuildTargetDependentFunctionAttributes(context); #endregion var diFile = module.DIBuilder.CreateFile(srcPath); #region CreatingBasicTypesWithDebugInfo // Create basic types used in this compilation var i32 = new DebugBasicType(module.Context.Int32Type, module, "int", DiTypeKind.Signed); var f32 = new DebugBasicType(module.Context.FloatType, module, "float", DiTypeKind.Float); var voidType = DebugType.Create <ITypeRef, DIType>(module.Context.VoidType, null); var i32Array_0_32 = i32.CreateArrayType(module, 0, 32); #endregion #region CreatingStructureTypes // create the LLVM structure type and body with full debug information var fooBody = new[] { new DebugMemberInfo(0, "a", diFile, 3, i32), new DebugMemberInfo(1, "b", diFile, 4, f32), new DebugMemberInfo(2, "c", diFile, 5, i32Array_0_32), }; var fooType = new DebugStructType(module, "struct.foo", module.DICompileUnit, "foo", diFile, 1, DebugInfoFlags.None, fooBody); #endregion #region CreatingGlobalsAndMetadata // add global variables and constants var constArray = ConstantArray.From(i32, 32, module.Context.CreateConstant(3), module.Context.CreateConstant(4)); var barValue = module.Context.CreateNamedConstantStruct(fooType , module.Context.CreateConstant(1) , module.Context.CreateConstant(2.0f) , constArray ); var bar = module.AddGlobal(fooType, false, 0, barValue, "bar"); bar.Alignment = module.Layout.AbiAlignmentOf(fooType); bar.AddDebugInfo(module.DIBuilder.CreateGlobalVariableExpression(module.DICompileUnit, "bar", string.Empty, diFile, 8, fooType.DIType, false, null)); var baz = module.AddGlobal(fooType, false, Linkage.Common, Constant.NullValueFor(fooType), "baz"); baz.Alignment = module.Layout.AbiAlignmentOf(fooType); baz.AddDebugInfo(module.DIBuilder.CreateGlobalVariableExpression(module.DICompileUnit, "baz", string.Empty, diFile, 9, fooType.DIType, false, null)); // add module flags and compiler identifiers... // this can technically occur at any point, though placing it here makes // comparing against clang generated files easier AddModuleFlags(module); #endregion #region CreatingQualifiedTypes // create types for function args var constFoo = module.DIBuilder.CreateQualifiedType(fooType.DIType, QualifiedTypeTag.Const); var fooPtr = new DebugPointerType(fooType, module); #endregion // Create the functions // NOTE: The declaration ordering is reversed from that of the sample code file (test.c) // However, this is what Clang ends up doing for some reason so it is // replicated here to aid in comparing the generated LL files. IrFunction doCopyFunc = DeclareDoCopyFunc(module, diFile, voidType); IrFunction copyFunc = DeclareCopyFunc(module, diFile, voidType, constFoo, fooPtr); CreateCopyFunctionBody(module, copyFunc, diFile, fooType, fooPtr, constFoo); CreateDoCopyFunctionBody(module, doCopyFunc, fooType, bar, baz, copyFunc); // finalize the debug information // all temporaries must be replaced by now, this resolves any remaining // forward declarations and marks the builder to prevent adding any // nodes that are not completely resolved. module.DIBuilder.Finish( ); // verify the module is still good and print any errors found if (!module.Verify(out string msg)) { Console.Error.WriteLine("ERROR: {0}", msg); } else { // test optimization works, but don't save it as that makes it harder to do a compare with official clang builds {// force a GC to verify callback delegate for diagnostics is still valid, this is for test only and wouldn't // normally be done in production code. GC.Collect(GC.MaxGeneration); using var modForOpt = module.Clone( ); // NOTE: // The ordering of passes can matter depending on the pass, and passes may be added more than once // the caller has full control of ordering, this is just a sample of effectively randomly picked // passes and not necessarily a reflection of any particular use case. using var pm = new ModulePassManager( ); pm.AddAlwaysInlinerPass( ) .AddAggressiveDCEPass( ) .AddArgumentPromotionPass( ) .AddBasicAliasAnalysisPass( ) .AddBitTrackingDCEPass( ) .AddCFGSimplificationPass( ) .AddConstantMergePass( ) .AddConstantPropagationPass( ) .AddFunctionInliningPass( ) .AddGlobalOptimizerPass( ) .AddInstructionCombiningPass( ) .Run(modForOpt); } // Module is good, so generate the output files module.WriteToFile(Path.Combine(outputPath, "test.bc")); File.WriteAllText(Path.Combine(outputPath, "test.ll"), module.WriteToString( )); TargetDetails.TargetMachine.EmitToFile(module, Path.Combine(outputPath, "test.o"), CodeGenFileType.ObjectFile); TargetDetails.TargetMachine.EmitToFile(module, Path.Combine(outputPath, "test.s"), CodeGenFileType.AssemblySource); Console.WriteLine("Generated test.bc, test.ll, test.o, and test.s"); } }
public bool Describes(IrFunction function) => LibLLVMSubProgramDescribes(MetadataHandle, function.ValidateNotNull(nameof(function)).ValueHandle);
private static void CreateCopyFunctionBody(BitcodeModule module , IrFunction copyFunc , DIFile diFile , ITypeRef foo , DebugPointerType fooPtr , DIType constFooType ) { Debug.Assert(copyFunc.DISubProgram != null, "Expected function with a valid debug subprogram"); var diBuilder = module.DIBuilder; copyFunc.Parameters[0].Name = "src"; copyFunc.Parameters[1].Name = "pDst"; // create block for the function body, only need one for this simple sample var blk = copyFunc.AppendBasicBlock("entry"); // create instruction builder to build the body var instBuilder = new InstructionBuilder(blk); // create debug info locals for the arguments // NOTE: Debug parameter indices are 1 based! var paramSrc = diBuilder.CreateArgument(copyFunc.DISubProgram, "src", diFile, 11, constFooType, false, 0, 1); var paramDst = diBuilder.CreateArgument(copyFunc.DISubProgram, "pDst", diFile, 12, fooPtr.DIType, false, 0, 2); uint ptrAlign = module.Layout.CallFrameAlignmentOf(fooPtr); // create Locals // NOTE: There's no debug location attached to these instructions. // The debug info will come from the declare intrinsic below. var dstAddr = instBuilder.Alloca(fooPtr) .RegisterName("pDst.addr") .Alignment(ptrAlign); bool param0ByVal = copyFunc.Attributes[FunctionAttributeIndex.Parameter0].Contains(AttributeKind.ByVal); if (param0ByVal) { diBuilder.InsertDeclare(copyFunc.Parameters[0] , paramSrc , new DILocation(module.Context, 11, 43, copyFunc.DISubProgram) , blk ); } instBuilder.Store(copyFunc.Parameters[1], dstAddr) .Alignment(ptrAlign); // insert declare pseudo instruction to attach debug info to the local declarations diBuilder.InsertDeclare(dstAddr, paramDst, new DILocation(module.Context, 12, 38, copyFunc.DISubProgram), blk); if (!param0ByVal) { // since the function's LLVM signature uses a pointer, which is copied locally // inform the debugger to treat it as the value by dereferencing the pointer diBuilder.InsertDeclare(copyFunc.Parameters[0] , paramSrc , diBuilder.CreateExpression(ExpressionOp.Deref) , new DILocation(module.Context, 11, 43, copyFunc.DISubProgram) , blk ); } var loadedDst = instBuilder.SetDebugLocation(15, 6, copyFunc.DISubProgram) .Load(fooPtr, dstAddr) .Alignment(ptrAlign); instBuilder.SetDebugLocation(15, 13, copyFunc.DISubProgram); var dstPtr = instBuilder.BitCast(loadedDst, module.Context.Int8Type.CreatePointerType( )); var srcPtr = instBuilder.BitCast(copyFunc.Parameters[0], module.Context.Int8Type.CreatePointerType( )); uint pointerSize = module.Layout.IntPtrType(module.Context).IntegerBitWidth; instBuilder.MemCpy(dstPtr , srcPtr , module.Context.CreateConstant(pointerSize, module.Layout.ByteSizeOf(foo), false) , false ); instBuilder.SetDebugLocation(16, 1, copyFunc.DISubProgram) .Return( ); }