Last active
June 14, 2019 22:46
-
-
Save nasser/a1d99f258bfe5ecc357702ce4dac5431 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Generic Edtude\n", | |
| "\n", | |
| "Understanding generic bytecode emission with F# and Cecil" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 1, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "#r \"Mono.Cecil\"\n", | |
| "#r \"Mono.Cecil.Rocks\"" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 2, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "open System\n", | |
| "open System.Linq\n", | |
| "open System.Reflection\n", | |
| "open System.IO\n", | |
| "open Mono.Cecil\n", | |
| "open Mono.Cecil.Rocks\n", | |
| "open Mono.Cecil.Cil" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "ok here we go.\n", | |
| "\n", | |
| "generics, as i understand them, show up in the following places:\n", | |
| "\n", | |
| "1. type definitions, as generic type parameters\n", | |
| "2. method definitions, as generic metod parameters\n", | |
| "3. type construction (`newobj`, `initobj`)\n", | |
| "4. method invocation (`call`)\n", | |
| " 1. static\n", | |
| " 2. instance\n", | |
| "5. generic type field reference\n", | |
| "6. generic type property reference\n", | |
| "\n", | |
| "i *think* that's the whole story. the goal of this notebook is to figure out the correct way, at the bytecode level, to do the tasks in the above list. there are other challenges with generics, like syntax and inference/type flow, but thats not the concern here.\n", | |
| "\n", | |
| "here's the approach in mind: start with invocations/construction of existing generic types and methods and follow that with generic definitions, and finish with invoking/constructing the definitions we created in the 2nd step." | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "something thats going to come up is emitting modules into memory. there's a bit of a trick to it and we can wrap it up in a function now before we get started" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 3, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let writeToMemory (assembly:AssemblyDefinition) =\n", | |
| " use outStream = new MemoryStream()\n", | |
| " assembly.Write(outStream)\n", | |
| " Assembly.Load(outStream.GetBuffer())\n", | |
| "\n", | |
| "let writeToDisk (assembly:AssemblyDefinition) (file:string) =\n", | |
| " assembly.Write(file)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "we write the assembly to a stream in memory and then immediatley read that stream back through the CLR's reflection API. the resulting assembly is now usable!" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Generic Static Method Invocation" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "we want to compile the equivalent of:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 4, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "(5, \"hello\")" | |
| ] | |
| }, | |
| "execution_count": 4, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "Tuple.Create(5, \"hello\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "which, taking away type inference, is the following generic method invocation" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 5, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "(5, \"hello\")" | |
| ] | |
| }, | |
| "execution_count": 5, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "Tuple.Create<int32, string>(5, \"hello\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "to call `Tuple.Create` we need to import a reference to the method into the module we're working on. we could do this by leaning on CLR reflection, but to keep everything in Cecil lets load `mscorlib` as an `AssemblyReference` and find `Tuple.Create` there" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 6, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let mscorlibAssembly = AssemblyDefinition.ReadAssembly(typeof<Tuple>.Assembly.Location)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 7, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" | |
| ] | |
| }, | |
| "execution_count": 7, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "mscorlibAssembly" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 8, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let systemTupleType = mscorlibAssembly.MainModule.Types.Where(fun t -> t.Name = \"Tuple\").First()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 9, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "System.Tuple" | |
| ] | |
| }, | |
| "execution_count": 9, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "systemTupleType" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 10, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "seq\n", | |
| " [System.Tuple`1<T1> System.Tuple::Create(T1);\n", | |
| " System.Tuple`2<T1,T2> System.Tuple::Create(T1,T2);\n", | |
| " System.Tuple`3<T1,T2,T3> System.Tuple::Create(T1,T2,T3);\n", | |
| " System.Tuple`4<T1,T2,T3,T4> System.Tuple::Create(T1,T2,T3,T4); ...]" | |
| ] | |
| }, | |
| "execution_count": 10, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "systemTupleType.Methods" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "so we have a Cecil reference to the `System.Tuple` type and we can see some of its methods. we want to grab the 2-arity `Create` method for this etude. in a normal compiler it would be job of the binder to make this selection, but we can do it manually for our purposes here." | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "### Attempt #1 (failure)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 11, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let gsmiAssembly =\n", | |
| " AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition(\"GenericsNotebook\", new Version(0, 0)), \"GenericsNotebook\", ModuleKind.Dll)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 12, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let gsmiType = new TypeDefinition(\"Etude\", \"GenericStaticMethodInvocation\", TypeAttributes.Public ||| TypeAttributes.Class, gsmiAssembly.MainModule.TypeSystem.Object)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 13, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "gsmiAssembly.MainModule.Types.Add(gsmiType)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "given that we can begin. to start we're going to try and compile the equivalent of\n", | |
| "\n", | |
| "```cs\n", | |
| "void VoidMethod()\n", | |
| "{\n", | |
| " Tuple.Create(5, \"hello\");\n", | |
| "}\n", | |
| "```\n", | |
| "\n", | |
| "that is, create and the immediatley pop a generic value." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 14, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let voidMethod = new MethodDefinition(\"VoidMethod\", MethodAttributes.Public ||| MethodAttributes.Static, gsmiAssembly.MainModule.TypeSystem.Void)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 15, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_5))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 16, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldstr, \"hello\"))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "So far we've just pushed the arguments onto the stack, nothing exceptional yet." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 17, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "seq [IL_0000: ldc.i4.5; IL_0000: ldstr \"hello\"]" | |
| ] | |
| }, | |
| "execution_count": 17, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "voidMethod.Body.Instructions" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 18, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let systemTupleCreate2Method = systemTupleType.Methods.Where(fun m -> m.Name = \"Create\" && m.Parameters.Count = 2).First()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 19, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "System.Tuple`2<T1,T2> System.Tuple::Create(T1,T2)" | |
| ] | |
| }, | |
| "execution_count": 19, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "systemTupleCreate2Method" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "cool. now we need to emit a call to this method in `VoidMethod` and then pop the result. straightforward approach first." | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "important and easy to forget: methods and types need to be *imported* into the modules that refer to them. in a compiler you would abstract this away because you *always* need to do it and you only find out if you messed it up *after* you emit the code..." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 20, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let systemTupleCreate2MethodImported = gsmiAssembly.MainModule.ImportReference systemTupleCreate2Method" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 21, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Call, systemTupleCreate2MethodImported))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 22, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Pop))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 23, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "seq\n", | |
| " [IL_0000: ldc.i4.5; IL_0000: ldstr \"hello\";\n", | |
| " IL_0000: call System.Tuple`2<T1,T2> System.Tuple::Create(T1,T2); IL_0000: pop]" | |
| ] | |
| }, | |
| "execution_count": 23, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "voidMethod.Body.Instructions" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "conceptually that looks OK. lets see if it works." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 24, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "gsmiType.Methods.Add(voidMethod)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 25, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "seq [System.Void Etude.GenericStaticMethodInvocation::VoidMethod()]" | |
| ] | |
| }, | |
| "execution_count": 25, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "gsmiType.Methods" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 26, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let gsmiFinalAssembly = writeToMemory gsmiAssembly" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 27, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "GenericsNotebook, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" | |
| ] | |
| }, | |
| "execution_count": 27, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "gsmiFinalAssembly" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 28, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let gsmiFinalVoidMethod = gsmiFinalAssembly.GetType(\"Etude.GenericStaticMethodInvocation\").GetMethod(\"VoidMethod\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "trying to invoke this method crashes the VM. lets write the assembly out to disk and see what `peverify` says." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 29, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "writeToDisk gsmiAssembly \"gsmi.dll\"" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "```\n", | |
| "In method: Etude.GenericStaticMethodInvocation::VoidMethod()\n", | |
| "Error: Invalid generic type (!!System.Tuple`2<!!0, !!1>) (argument out of range or method is not generic) at 0x0006\n", | |
| "Not Verifiable: Incompatible parameter with function signature: Calling method with signature (!!0) but for argument 0 there is a (int (Int32)) on stack at 0x0006\n", | |
| "Not Verifiable: Incompatible parameter with function signature: Calling method with signature (!!1) but for argument 1 there is a (string (Complex)) on stack at 0x0006\n", | |
| "Error count: 1\n", | |
| "```" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "yup, bad bytecode. the straightforward approach does not work." | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "### Attempt #2 (success)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "we're going to need an assembly, module, type, and method again" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 30, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let gsmiAssembly =\n", | |
| " AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition(\"GenericsNotebook\", new Version(0, 0)), \"GenericsNotebook\", ModuleKind.Dll)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 31, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let gsmiType = new TypeDefinition(\"Etude\", \"GenericStaticMethodInvocation\", TypeAttributes.Public ||| TypeAttributes.Class, gsmiAssembly.MainModule.TypeSystem.Object)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 32, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "gsmiAssembly.MainModule.Types.Add(gsmiType)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 33, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let voidMethod = new MethodDefinition(\"VoidMethod\", MethodAttributes.Public ||| MethodAttributes.Static, gsmiAssembly.MainModule.TypeSystem.Void)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 34, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_5))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 35, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldstr, \"hello\"))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 36, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "gsmiType.Methods.Add(voidMethod)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "we're going to import a reference to the method as before, but this time we're going to create a `GenericInstanceMethod` out of it. `GenericInstanceMethods` allow us to assign concrete values to generic parameters." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 37, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let systemTupleCreate2MethodImported = gsmiAssembly.MainModule.ImportReference systemTupleCreate2Method" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 38, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let systemTupleCreateIntString = new Mono.Cecil.GenericInstanceMethod(systemTupleCreate2MethodImported)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 39, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "System.Tuple`2<T1,T2> System.Tuple::Create<>(T1,T2)" | |
| ] | |
| }, | |
| "execution_count": 39, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "systemTupleCreateIntString" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "note the `<>` in `System.Tuple::Create<>`, indicating generic parameters have not been provided" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 40, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let intType = gsmiAssembly.MainModule.TypeSystem.Int32" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 41, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let stringType = gsmiAssembly.MainModule.TypeSystem.String" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 42, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "systemTupleCreateIntString.GenericArguments.Add(intType)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 43, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "systemTupleCreateIntString.GenericArguments.Add(stringType)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 44, | |
| "metadata": { | |
| "scrolled": true | |
| }, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "System.Tuple`2<T1,T2> System.Tuple::Create<System.Int32,System.String>(T1,T2)" | |
| ] | |
| }, | |
| "execution_count": 44, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "systemTupleCreateIntString" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "note the filled in `<System.Int32,System.String>` in `System.Tuple::Create<System.Int32,System.String>` indicating filled in generic arguments. can we call this method now?" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 45, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Call, systemTupleCreateIntString))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 46, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Pop))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 47, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 48, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "seq\n", | |
| " [IL_0000: ldc.i4.5; IL_0000: ldstr \"hello\";\n", | |
| " IL_0000: call System.Tuple`2<T1,T2> System.Tuple::Create<System.Int32,System.String>(T1,T2);\n", | |
| " IL_0000: pop; ...]" | |
| ] | |
| }, | |
| "execution_count": 48, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "voidMethod.Body.Instructions" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 49, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "writeToDisk gsmiAssembly \"gsmi.dll\"" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "that passes verification! can we run the method?" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 50, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let gsmiFinalAssembly = writeToMemory gsmiAssembly" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 51, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "let voidMethodDelegate = gsmiFinalAssembly.GetType(\"Etude.GenericStaticMethodInvocation\").GetMethod(\"VoidMethod\").CreateDelegate(typeof<Action>) :?> Action" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 52, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "voidMethodDelegate.Invoke()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "yup 😎 (no errors!)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "#### Questions" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "* what is the return type of the instantiated `GenericInstanceMethod`?" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 53, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "System.Tuple`2<T1,T2>" | |
| ] | |
| }, | |
| "execution_count": 53, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "systemTupleCreateIntString.ReturnType" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "it still has uninstantiated generic parameters! put another way, Cecil does not flow this information into the return type. not sure why, maybe its a spec conformance thing? this means `ReturnType` is really only useful for non-generic methods. we'd want it to return `Tuple<int, string>` in this case." | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "* what are the parameter types of the instantiated `GenericInstanceMethod`?" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 54, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "seq [T1; T2]" | |
| ] | |
| }, | |
| "execution_count": 54, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "systemTupleCreateIntString.Parameters.Select(fun p -> p.ParameterType)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "similarly, unstantiated types. this is not useful for type flow and will have to be wrapped. we'd want to see `seq [System.Int32; System.String]` in this case." | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "#### Conclusion\n", | |
| "* generic methods cannot appear directly in bytecode\n", | |
| "* they must be wrapped in `Mono.Cecil.GenericInstanceMethod`\n", | |
| "* `GenericInstanceMethod` exposes a `GenericArguments` collection which should be populated by the generic concrete arguments known at the call site\n", | |
| "* `ReturnType` and `Parameters` are not affected by assigning `GenericArguments` and continue to report generic parameter types." | |
| ] | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "display_name": "F#", | |
| "language": "fsharp", | |
| "name": "ifsharp" | |
| }, | |
| "language": "fsharp", | |
| "language_info": { | |
| "codemirror_mode": "", | |
| "file_extension": ".fs", | |
| "mimetype": "text/x-fsharp", | |
| "name": "fsharp", | |
| "nbconvert_exporter": "", | |
| "pygments_lexer": "", | |
| "version": "4.3.1.0" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 2 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment