{"id":43,"date":"2009-11-04T17:35:23","date_gmt":"2009-11-05T00:35:23","guid":{"rendered":"http:\/\/log.jonriehl.com\/?p=43"},"modified":"2010-01-12T13:09:12","modified_gmt":"2010-01-12T20:09:12","slug":"embedding-llvm-assembly-in-mython","status":"publish","type":"post","link":"http:\/\/log.jonriehl.com\/?p=43","title":{"rendered":"Embedding LLVM Assembly in Mython"},"content":{"rendered":"<p>Today we&#8217;re going to look at how we can use Mython and llvm-py to embed LLVM assembly code into a Mython module.\u00c2\u00a0 For those not familiar with Mython, I wouldn&#8217;t worry too much; what we are doing should not look, nor work too different from the following bit of code (which requires <a href=\"http:\/\/www.python.org\/download\/releases\/2.5.4\/\">Python 2.5<\/a>, <a href=\"http:\/\/llvm.org\/\">LLVM<\/a>, and <a href=\"http:\/\/mdevan.nfshost.com\/llvm-py\/\">llvm-py<\/a> to work, by the way):<\/p>\n<pre>import StringIO, llvm, llvm.core, llvm.ee\r\nllvm_asm = \"\"\"\r\n@msg = internal constant [15 x i8] c\"Hello, world.\\\\0A\\\\00\"\r\n\r\ndeclare i32 @puts(i8 *)\r\n\r\ndefine i32 @not_really_main() {\r\n    %cst = getelementptr [15 x i8]* @msg, i32 0, i32 0\r\n    call i32 @puts(i8 * %cst)\r\n    ret i32 0\r\n}\r\n\"\"\"\r\nllvm_module = llvm.core.Module.from_assembly(\r\n                  StringIO.StringIO(llvm_asm))\r\nmp = llvm.core.ModuleProvider.new(llvm_module)\r\nee = llvm.ee.ExecutionEngine.new(mp)\r\nnot_really_main = llvm_module.get_function_named(\r\n                      'not_really_main')\r\nee.run_function(not_really_main, [])<\/pre>\n<p>This code first defines an LLVM module in a Python string, then builds a LLVM module from the embedded code, and finally uses a JIT to link and run a function from the embedded module.\u00c2\u00a0 I would note two things about this little demo.\u00c2\u00a0 One, while the multiline string allows un-escaped quote characters, developers must still take care to escape backslashes.\u00c2\u00a0 Failing to do this causes the LLVM assembler to reject the string literal.\u00c2\u00a0 Two, the user of this code pays for the assembly of the LLVM code each time it is run.\u00c2\u00a0 Both of these are relatively minor problems, but they illustrate why a developer might prefer Mython over embedding another language as strings in a Python file.\u00c2\u00a0 Later, we shall develop these arguments in more depth.<\/p>\n<p>This post demonstrates how we can take the infrastructure in llvm-py and use it to embed LLVM source.\u00c2\u00a0 We show how to assemble the embedded LLVM source into LLVM bitcode at compile time.\u00c2\u00a0 We&#8217;ll then stash the bitcode for consumption by the LLVM JIT compiler and linker at run time.\u00c2\u00a0 This approach saves us from the bitcode compilation time, and ideally saves some space in the Python bytecode.\u00c2\u00a0 More importantly, this approach ensures that errors in the embedded source are detected at compile time, not run time.<\/p>\n<h1>Preliminaries<\/h1>\n<p>If you&#8217;re not terribly familiar with Python, LLVM, and llvm-py, I&#8217;d recommend reading at least the Python tutorial, the LLVM assembly tutorial, and the llvm-py user guide.\u00c2\u00a0 The llvm-py user guide should, in turn, point you at a specific test case for using the LLVM JIT, which the above code follows except it builds the module from assembly source.\u00c2\u00a0 The llvm-py documentation builds the module using wrapper objects for the LLVM intermediate representation (IR).<\/p>\n<p>At the time of writing, I used LLVM 2.5 (via <a href=\"http:\/\/www.macports.org\/\">MacPorts<\/a>), and built llvm-py from the <a href=\"http:\/\/code.google.com\/p\/llvm-py\/source\/checkout\">Google code repository<\/a>.\u00c2\u00a0 Originally, I tried the llvm-py port, but the llvm-py 0.5 tarball they use doesn&#8217;t build against LLVM 2.5.\u00c2\u00a0 I encountered this problem with llvm-py 0.5 again on <a href=\"http:\/\/cygwin.org\/\">Cygwin<\/a>, this time doing a manual build and install of LLVM 2.5 from a source tarball.\u00c2\u00a0 I was also able to build and install the llvm-py Subversion head, but the example code for this post does not work (it can&#8217;t dynamically resolve <code>puts()<\/code>).<\/p>\n<p>Mython introduces a special form of quotation into the Python language.\u00c2\u00a0 The idea is that you can embed raw strings in your source code, and these strings are interpreted into Python code at compile time.\u00c2\u00a0 Quotation blocks look something like this:<\/p>\n<pre>quote [quotefn] name:\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 ...<\/pre>\n<p>The <code>quotefn()<\/code> is ideally a function that takes a name, a string, and a dictionary, and returns a 2-tuple containing a list of Python abstract syntax trees (AST&#8217;s, specifically, statement nodes), and a dictionary.\u00c2\u00a0 Instead of giving a quick demonstration of how to define and use a quotation function in Mython, let&#8217;s go ahead and demonstrate these by embedding LLVM assembly.\u00c2\u00a0 I will explain the Mython code as we go along.<\/p>\n<p>I recommend you grab a copy of MyFront (which is part of the Basil language framework), and the <code>test1.my<\/code> source file from the <a href=\"http:\/\/code.google.com\/p\/basil\/source\/checkout\">Google Code repository<\/a> (see availability, below).\u00c2\u00a0 The following discussion essentially gives the source code for <code>test1.my<\/code>, but lists it out of order.<\/p>\n<h1>Interfacing llvm-py and Mython<\/h1>\n<p>So now that I&#8217;ve discussed the preliminaries, let&#8217;s just go ahead and start defining the driver we&#8217;ll use to test the compile-time wrapper for the LLVM assembler.\u00c2\u00a0 Let&#8217;s assume that we already have a &#8220;quotation&#8221; function for LLVM assembly.<\/p>\n<pre>quote [llvm_as] llvm_module:\r\n @msg = internal constant [15 x i8] c\"Hello, world.\\0A\\00\"\r\n declare i32 @puts(i8 *)\r\n define i32 @not_really_main() {\r\n     %cst = getelementptr [15 x i8]* @msg, i32 0, i32 0\r\n     call i32 @puts(i8 * %cst)\r\n     ret i32 0\r\n }<\/pre>\n<p>Our job consists of defining <code>llvm_as()<\/code> to be a quotation function that translates this quotation block into something like the following:<\/p>\n<pre>llvm_module = llvm.core.Module.from_bitcode(\r\n                  StringIO.StringIO(\"...\"))<\/pre>\n<p>At run time, the above constructs a LLVM module from the elided bitcode in the string literal (the <code>\"...\"<\/code>).\u00c2\u00a0 We therefore need to define a compile-time function that does the following:<\/p>\n<ul>\n<li>Takes the embedded source code and assembles it into an LLVM module.<\/li>\n<li>Translates the LLVM module into a string literal containing LLVM bitcode.<\/li>\n<li>Compiles a Python abstract syntax tree that will reconstruct the LLVM module from the embedded bitcode.<\/li>\n<\/ul>\n<p>Before we proceed, let us assume that we already have three bound variables, each corresponding to a quotation function parameter: <code>name<\/code>, <code>source<\/code>, and <code>env<\/code>.\u00c2\u00a0 The <code>name<\/code> variable is bound to the string literal <code>\"llvm_module\"<\/code>.\u00c2\u00a0 The <code>source<\/code> variable contains the string of the LLVM assembly, with the leading indentation white space removed.\u00c2\u00a0 The <code>env<\/code> variable is a dictionary that is supposed to be an explicit replacement of the <code>__globals__<\/code> dictionary, originally used by Python to manage its global namespace, but passed by MyFront explicitly as a reminder that it is a compile-time environment, not a run-time environment.\u00c2\u00a0 I&#8217;m not sure if this &#8220;explicit store passing&#8221; style actually buys us anything, and this may be dropped from quotation functions in later versions of Mython.<\/p>\n<p>We&#8217;ve seen some of the above steps accomplished in the introduction.\u00c2\u00a0 We first must build a LLVM module from the LLVM assembly code, which is bound to the <code>source<\/code> variable:<\/p>\n<pre>fobj1 = StringIO.StringIO(source)\r\nllvm_module = llm.core.Module.from_assembly(fobj1)<\/pre>\n<p>We now have the same module we&#8217;ll want to use at run time bound at compile time (actually its a functionally identical module).\u00c2\u00a0 We need to emit the bitcode that we&#8217;re going to embed in the run time code we&#8217;ll be generating:<\/p>\n<pre>fobj2 = StringIO.StringIO()\r\nllvm_module.to_bitcode(fobj2)<\/pre>\n<p>This writes the bitcode as a string literal within the <code>StringIO<\/code> file abstraction.\u00c2\u00a0 We can now build Python code in another string:<\/p>\n<pre>runtime_src = (\"%s = llvm.core.Module.from_bitcode(\"\r\n               \"StringIO.StringIO(%r))\\n\" %\r\n               (name, fobj2.getvalue()))<\/pre>\n<p>Normally, I would expect the next step to be a possibly involved process of walking over some intermediate representation and constructing a Python AST to pass back to the compiler.\u00c2\u00a0 In this case, we can avoid having to do this, since all we need to do is embed the LLVM code as a string argument.\u00c2\u00a0 To convert the run-time code into an AST, we are going to take advantage of the fact that the compiler reflects its front-end in the <code>env<\/code> dictionary.\u00c2\u00a0 MyFront maps the string <code>\"myfrontend\"<\/code> to a function that translates Mython source code and the compile-time environment into a Python AST, and a possibly mutated compile-time environment.\u00c2\u00a0 This function allows us to simply take the above string and parse it into a Python AST like so:<\/p>\n<pre>runtime_ast, env = env[\"myfrontend\"](runtime_src, env)<\/pre>\n<p>The <code>myfrontend()<\/code> function specifically returns a <code>Module<\/code> AST node.\u00c2\u00a0 In order to get a list of statement AST nodes, we&#8217;ll just have to look at the <code>body<\/code> member of the returned <code>Module<\/code> object.\u00c2\u00a0 The fully wrapped up Mython quotation function looks like this:<\/p>\n<pre>quote [myfront]:\r\n    def llvm_as (name, source, env):\r\n        assert name is not None\r\n        fobj1 = StringIO.StringIO(source)\r\n        llvm_module = llvm.core.Module.from_assembly(fobj1)\r\n        fobj2 = StringIO.StringIO()\r\n        llvm_module.to_bitcode(fobj2)\r\n        runtime_src = (\"%s = llvm.core.Module.from_bitcode(\"\r\n                       \"StringIO.StringIO(%r))\\n\" %\r\n                       (name, fobj2.getvalue()))\r\n        runtime_ast, env = env[\"myfrontend\"](runtime_src, env)\r\n        return runtime_ast.body, env<\/pre>\n<p>If you are curious about the above quotation block, the <code>myfront()<\/code> quotation function simply evaluates the embedded code at compile time and in the compile-time environment.\u00c2\u00a0 This allows us to define the <code>llvm_as()<\/code> function at compile time, but then throw it away at run time.<\/p>\n<p>The only thing that is left is to test it:<\/p>\n<pre>def main ():\r\n    import llvm.ee\r\n    print llvm_module\r\n    print \"_\" * 60\r\n    provider = llvm.core.ModuleProvider.new(llvm_module)\r\n    llvm_engine = llvm.ee.ExecutionEngine.new(provider)\r\n    not_really_main = llvm_module.get_function_named(\r\n                          'not_really_main')\r\n    retval = llvm_engine.run_function(not_really_main, [])\r\n    print \"_\" * 60\r\n    print \"Returned\", retval.as_int()\r\n\r\nif __name__ == \"__main__\":\r\n    main()<\/pre>\n<p>When I run this on my Mac (again, this is all in the <code>test01.my<\/code> source file), I see the following (the lines that start with &#8220;$&#8221; show command line inputs):<\/p>\n<pre>$ MyFront test1.my\r\n$ python -m test1\r\n@msg = internal constant [15 x i8] c\"Hello, world.\\0A\\00\"\\\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 ; &lt;[15 x i8]*&gt; [#uses=1]\r\n\r\ndeclare i32 @puts(i8*)\r\n\r\ndefine i32 @not_really_main() {\r\n %cst = getelementptr [15 x i8]* @msg, i32 0, i32 0\\\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 ; &lt;i8*&gt; [#uses=1]\r\n %1 = call i32 @puts(i8* %cst)\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 ; &lt;i32&gt; [#uses=0]\r\n ret i32 0\r\n}\r\n\r\n____________________________________________________________\r\nHello, world.\r\n\r\n____________________________________________________________\r\nReturned 0<\/pre>\n<p>I was not able to get LLVM to dynamically link <code>puts()<\/code> on the <a href=\"http:\/\/cygwin.org\/\">Cygwin<\/a> platform.\u00c2\u00a0 The resulting runtime code correctly outputs the module source, but then raises a signal, causing a core dump.\u00c2\u00a0 It would be nice if the abort signal was simply thrown as an exception.\u00c2\u00a0 I am reminded of the utility of <a href=\"http:\/\/www.dabeaz.com\/cv.html\">David Beazley<\/a>&#8216;s wrapped application debugger (WAD), or something similar, for catching signals and then translating them to Python exceptions.<\/p>\n<h1>Discussion<\/h1>\n<p>Now that we have looked at how to embed LLVM assembly in Mython, let&#8217;s look more closely at possibilities for answering why you would want to use Mython&#8217;s approach.\u00c2\u00a0 This section looks at three things.\u00c2\u00a0 First, it compares code size at the module level.\u00c2\u00a0 Second, it gives measurements and discusses any possible differences in the run-time performance.  Finally, this section demonstrates how both approaches to embedding handle errors in the embedded assembly.<\/p>\n<p>I did not expect the resulting module sizes.\u00c2\u00a0 The Python version, <code>test0.py<\/code>, compiles to a file, <code>test0.pyc<\/code>, which is 1,252 bytes in size.\u00c2\u00a0 The Mython version compiles to <code>test1.pyc<\/code>, and is 1,335 bytes big.\u00c2\u00a0 However, when I use <code>llvm-as<\/code> on the assembly code alone, I see that without comments, the assembly code is smaller than the LLVM bitcode file by 79 bytes (213 bytes for the source code, 292 bytes for the bitcode).  I assume that for more complicated input source, the LLVM bitcode will be smaller than the source (one can always skew this by adding comments; the original standalone <code>hello.ll<\/code> was 538 bytes with white space and comments).<\/p>\n<p>To compare the run-time performance of the naive and Mython embeddings, I created a test harness to measure three scenarios:<\/p>\n<ol>\n<li>The time it takes to construct an LLVM module from assembly source.\u00c2\u00a0 This should be representative of the time taken by a naive embedding.<\/li>\n<li>The time it takes to construct an LLVM module from assembly source and then serialize it into LLVM bitcode.\u00c2\u00a0 This should reflect the compile-time cost of the Mython embedding.<\/li>\n<li>The time it takes to construct an LLVM module from bitcode.  This reflects the run-time code of the Mython embedding.<\/li>\n<\/ol>\n<p>I implemented this test harness in <code>test2.py<\/code>, which can be found in the same repository as the other two test modules (see availability, below).\u00c2\u00a0 I am seeing the following results output from the test harness (times are in seconds, and reflect the minimum, maximum, and average times over 100 measurements of a function that performs the given test a 1000 times):<\/p>\n<pre>$ .\/test2.py\r\nNaive embedding summary: min=0.0542359 max=0.0596418 avg=0.0549726\r\nCompile-time summary: min=0.134633 max=0.150107 avg=0.136246\r\nRun-time summary: min=0.069649 max=0.0750451 avg=0.0704705<\/pre>\n<p>These results come as a second surprise.\u00c2\u00a0 Since the test harness solely runs wrapped LLVM code, it might seem that the LLVM infrastructure handles string inputs slightly faster than bitcode.  After thinking about this for a minute, a more likely explanation is that the bitcode input is larger than the assembly string input.  Using the sizes given above, we can see the bitcode string is about 1.37 times larger than the assembly source.\u00c2\u00a0 The module construction time is only about 1.28 times longer.\u00c2\u00a0 These relative numbers imply that if I did use more complicated assembly source with equivalent or smaller resulting bitcode, I would see a slight performance increase.  This run-time performance increase would come at a small additional cost at compile time.\u00c2\u00a0 On my machine, these numbers imply it would take an additional 13.6 milliseconds per 1000 lines of embedded assembly code (not counting deallocation time).<\/p>\n<p>Finally, we look at what happens when there is a syntax error in the embedded code.\u00c2\u00a0 In the repository, I copied the <code>test0.py<\/code> and <code>test1.my<\/code> files to the <code>bad0.py<\/code> and <code>bad1.my<\/code>, respectively.\u00c2\u00a0 I then remove the leading &#8220;<code>@<\/code>&#8221; from the function definition.\u00c2\u00a0 Here is the result of compiling these two modules using the MyFront compiler (note that I&#8217;ve hand shortened the file paths using ellipses):<\/p>\n<pre>$ rm *.pyc\r\n$ MyFront bad0.py\r\n$ MyFront bad1.my\r\nError in quote-generated code, from block starting at line 41:\r\n  Traceback (most recent call last):\r\n    File \"...\/basil\/lang\/mython\/MythonRewriter.py\", line 106, in\r\nhandle_QuoteDef\r\n    ret_val, env = quotefn(node.name, node.body, env)\r\n    File \"bad1.my\", line 4, in llvm_as\r\n    File \"...\/site-packages\/llvm\/core.py\", line 330, in from_assembly\r\n    raise llvm.LLVMException, ret\r\n  LLVMException: expected function name\r\n$ ls *.pyc\r\nbad0.pyc<\/pre>\n<p>I have chosen to focus on just using the compiler, so you can clearly see that the naive embedding was quietly compiled into a Python bytecode file.\u00c2\u00a0 In this particular case, the LLVM error would be caught at import time:<\/p>\n<pre>$ python -m bad0\r\nTraceback (most recent call last):\r\n  File \"...\/runpy.py\", line 95, in run_module\r\n    filename, loader, alter_sys)\r\n  File \"...\/runpy.py\", line 52, in _run_module_code\r\n    mod_name, mod_fname, mod_loader)\r\n  File \"...\/runpy.py\", line 32, in _run_code\r\n    exec code in run_globals\r\n  File \"...\/sandbox\/llvm\/bad0.py\", line 27, in\r\n    llvm_module = llvm.core.Module.from_assembly(StringIO.StringIO(\r\nllvm_source))\r\n  File \"...\/site-packages\/llvm\/core.py\", line 330, in from_assembly\r\n    raise llvm.LLVMException, ret\r\nllvm.LLVMException: expected function name<\/pre>\n<p>If you were to just compile this file and ship it, you might be condemning users to a nasty surprise.\u00c2\u00a0 I know you&#8217;d still catch these kinds of bugs by extensive testing, right?\u00c2\u00a0 The specific bug I&#8217;ve injected would be pretty easy to find, since the exception would occur as soon as you import the module.\u00c2\u00a0 If you assembled the LLVM code inside a function, or on some special path, these kinds of bugs become much harder to find.\u00c2\u00a0 You would have to be especially careful if the LLVM source was automatically generated.<\/p>\n<p>I am slightly embarrassed to note that this kind of experiment can still go horribly wrong in Mython.\u00c2\u00a0 Since the current Mython implementation uses the Python <code>tokenize<\/code> module, it will not detect a <code>DEDENT<\/code> token if your embedded code has imbalanced brackets, braces, or parentheses.\u00c2\u00a0 Feel free to delete the close brace from the embedded LLVM and watch the resulting mess output by MyFront&#8217;s recursive descent parser.\u00c2\u00a0 I hope to have <a href=\"http:\/\/code.google.com\/p\/basil\/issues\/detail?id=14\">this problem<\/a> fixed<br \/>\nshortly.<\/p>\n<h1>Conclusion<\/h1>\n<p>To conclude, I was really hoping to make the following claim:<\/p>\n<ul>\n<li>We can embed LLVM bitcode in Python, and this should offer our compiled modules greater speed without sacrificing platform independence.<\/li>\n<\/ul>\n<p>In this case, I was not able to make this claim.\u00c2\u00a0 The idea is that the time necessary to parse a string and create a LLVM module should be less than the time necessary to construct a module from a bitcode string of equal size.\u00c2\u00a0 This claim might be easier to show for embeddings of native machine code, but that would cost us platform independence.\u00c2\u00a0 I would be interested in learning more about the LLVM bitcode format, and determining when it is likely that the bitcode for a module is larger than its source code (our example has a string literal in it, which might play some part in the source and bitcode sizes).<\/p>\n<p>I hope the following claims are easier to accept given this example:<\/p>\n<ul>\n<li>Mython makes it possible to embed code from other languages without string escapes.<\/li>\n<li>Mython makes it possible to check embedded code at compile time.<\/li>\n<li>If you already have a language implementation that can interface with Python, it is very simple (&lt; 10 lines of code) to embed and statically check it in Mython.<\/li>\n<\/ul>\n<p>I hope you will take the time to play around with building more quotation functions in Mython, and see what you can do with them.\u00c2\u00a0 I think quotation functions are a powerful mechanism for metaprogramming, and I hope to continue to provide interesting examples of their utility.<\/p>\n<h1>Availability<\/h1>\n<p>Instructions for obtaining Mython, and its implementation, the MyFront compiler, are given here: <a href=\"http:\/\/code.google.com\/p\/basil\/wiki\/GettingStarted\">http:\/\/code.google.com\/p\/basil\/wiki\/GettingStarted<br \/>\n<\/a><br \/>\nThe source code for the Python and Mython demonstration and test modules are in the Basil framework sandbox.\u00c2\u00a0 You can get them from Google Code here: <a href=\"http:\/\/code.google.com\/p\/basil\/source\/browse\/trunk\/sandbox\/llvm\/\">http:\/\/code.google.com\/p\/basil\/source\/browse\/trunk\/sandbox\/llvm\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today we&#8217;re going to look at how we can use Mython and llvm-py to embed LLVM assembly code into a Mython module.\u00c2\u00a0 For those not familiar with Mython, I wouldn&#8217;t worry too much; what we are doing should not look, nor work too different from the following bit of code (which requires Python 2.5, LLVM, [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-43","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=\/wp\/v2\/posts\/43","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=43"}],"version-history":[{"count":21,"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=\/wp\/v2\/posts\/43\/revisions"}],"predecessor-version":[{"id":64,"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=\/wp\/v2\/posts\/43\/revisions\/64"}],"wp:attachment":[{"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=43"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=43"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/log.jonriehl.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=43"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}