Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
lua52vm-tools/doc/lua52vm.html
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
734 lines (726 sloc)
32.4 KB
This file contains 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
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
| <html xmlns="http://www.w3.org/1999/xhtml"> | |
| <head> | |
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
| <meta http-equiv="Content-Style-Type" content="text/css" /> | |
| <meta name="generator" content="pandoc" /> | |
| <title></title> | |
| <style type="text/css">code{white-space: pre;}</style> | |
| <style type=text/css> code{white-space:pre} </style> | |
| <style type=text/css> table{border-spacing: 10pt} </style> | |
| </head> | |
| <body> | |
| <head> | |
| <title> | |
| Block library for Lua 5.2 | |
| </title> | |
| <link rel="stylesheet" type="text/css" href="lua.css"> <link rel="stylesheet" type="text/css" href="manual.css"> <META HTTP-EQUIV="content-type" CONTENT="text/html; charset=utf-8"> | |
| </head> | |
| <hr> | |
| <h1> | |
| <a href="http://www.lua.org/"><img src="logo.gif" alt="" border="0"></a> Lua 5.2 Bytecode and Virtual Machine | |
| </h1> | |
| by Dirk Laurie | |
| <p> | |
| <small> Copyright © 2013. Freely available under the terms of the <a href="http://www.lua.org/license.html">Lua license</a>. </small> | |
| <hr> | |
| <p> | |
| <h2 id="preface">Preface</h2> | |
| <p>I wrote this because writing out something is the only way I can get to understand it for myself. It's aimed at readers who think they know the Lua 5.2 Reference Manual (LRM5.2) pretty well, have a little experience with the machine instructions of at least one machine (Knuth's <code>MIX</code> will do) and would like to do some tinkering with code for the Lua 5.2 Virtual Machine (VM5.2).</p> | |
| <p><strong>Thanks:</strong> I've borrowed a lot from Elijah Frederickson's site <a href="https://github.com/mlnlover11/LuaAssemblyTools">https://github.com/mlnlover11/LuaAssemblyTools</a> (LAT), which not only has a set of assembly tools for Lua 5.1 and Lua 5.2, but also a fairly complete collection of useful earlier stuff by other people, in particular, Kein-Hong Man's <em>A No-Frills Introduction to Lua 5.1 VM Instructions</em> (NFI), which despite being written for Lua 5.1 is still extremely useful. And of course, sooner or later you will feel compelled to read the very condensed but absolutely definitive comments in the Lua 5.2 source code, particularly <code>lopcodes.h</code> and <code>lundump.c</code>.</p> | |
| <h2 id="binary-chunks">Binary chunks</h2> | |
| <blockquote> | |
| <p>A chunk can be stored in a file or in a string inside the host program. To execute a chunk, Lua first precompiles the chunk into instructions for a virtual machine, and then it executes the compiled code with an interpreter for the virtual machine.</p> | |
| <p>Chunks can also be precompiled into binary form; see program <code>luac</code> for details. Programs in source and compiled forms are interchangeable; Lua automatically detects the file type and acts accordingly. — §3.3.2</p> | |
| </blockquote> | |
| <p>The first paragraph above is the only one in in LRM5.2 containing the phrase "virtual machine". Three standard library functions deal with binary chunks: <code>load</code>, <code>loadfile</code> and <code>string.dump</code>. That is as much as the user is allowed to see officially. In closed-source code that would be as much as the user would ever know.</p> | |
| <p>But Lua is open-source. Not only is the source code well commented: we are allowed to reverse-engineer. Let us try <code>luac</code> first.</p> | |
| <pre><code>$ luac | |
| luac: no input files given | |
| usage: luac [options] [filenames] | |
| Available options are: | |
| -l list (use -l -l for full listing) | |
| -o name output to file 'name' (default is "luac.out") | |
| -p parse only | |
| -s strip debug information | |
| -v show version information | |
| -- stop handling options | |
| - stop handling options and process stdin</code></pre> | |
| <p>Right, we will give it a Hello World program, slightly convoluted to make it interesting. After the second line, terminal input stops (I typed Control-D) and <code>luac</code> printout starts.</p> | |
| <pre><code>$ luac -l -l -v -s - | |
| Lua 5.2.1 Copyright (C) 1994-2012 Lua.org, PUC-Rio | |
| local hello = "Hello" | |
| print (hello.." World!") | |
| main <stdin:0,0> (7 instructions at 0x9984970) | |
| 0+ params, 4 slots, 1 upvalue, 1 local, 3 constants, 0 functions | |
| 1 [1] LOADK 0 -1 ; "Hello" | |
| 2 [2] GETTABUP 1 0 -2 ; _ENV "print" | |
| 3 [2] MOVE 2 0 | |
| 4 [2] LOADK 3 -3 ; " World!" | |
| 5 [2] CONCAT 2 2 3 | |
| 6 [2] CALL 1 2 1 | |
| 7 [2] RETURN 0 1 | |
| constants (3) for 0x9984970: | |
| 1 "Hello" | |
| 2 "print" | |
| 3 " World!" | |
| locals (1) for 0x9984970: | |
| 0 hello 2 8 | |
| upvalues (1) for 0x9984970: | |
| 0 _ENV 1 0</code></pre> | |
| <p>We'll discuss this output later: at this stage all I want you to see are four lists: lightly commented assembly code, constants, locals and upvalues.</p> | |
| <p>Now we go to Lua itself. <code>load</code> the same chunk, dump it to get the bytecode, write it to a file. I patched my Lua to give a prompt of three blanks instead of <code>></code> so that you can just cut-and-paste this code.</p> | |
| <pre><code>$ lua | |
| Lua 5.2.1 Copyright (C) 1994-2012 Lua.org, PUC-Rio | |
| func = load 'local hello = "Hello" print (hello.." World!")' | |
| chunk = string.dump(func) | |
| io.open("hello.luac","wb"):write(chunk)</code></pre> | |
| <p><code>luac</code> accepts binary chunks too. We can use it to take a look at what <code>load</code> did.</p> | |
| <pre><code>$ luac -l -l -v hello.luac | |
| Lua 5.2.1 Copyright (C) 1994-2012 Lua.org, PUC-Rio | |
| main <(string):0,0> (7 instructions at 0x90d0b50) | |
| 0+ params, 4 slots, 1 upvalue, 1 local, 3 constants, 0 functions | |
| 1 [1] LOADK 0 -1 ; "Hello" | |
| 2 [1] GETTABUP 1 0 -2 ; _ENV "print" | |
| 3 [1] MOVE 2 0 | |
| 4 [1] LOADK 3 -3 ; " World!" | |
| 5 [1] CONCAT 2 2 3 | |
| 6 [1] CALL 1 2 1 | |
| 7 [1] RETURN 0 1 | |
| constants (3) for 0x90d0b50: | |
| 1 "Hello" | |
| 2 "print" | |
| 3 " World!" | |
| locals (1) for 0x90d0b50: | |
| 0 hello 2 8 | |
| upvalues (1) for 0x90d0b50: | |
| 0 _ENV 1 0</code></pre> | |
| <p>Spot the differences?</p> | |
| <ul> | |
| <li><code>(string)</code> vs <code>stdin</code></li> | |
| <li><code>0x90d0b50</code> vs <code>0x9984970</code> in four places</li> | |
| <li><code>[1]</code> vs <code>[2]</code> on all but one of the instructions</li> | |
| </ul> | |
| <p>I.e. <code>load</code> and <code>luac</code> basically generate exactly the same code.</p> | |
| <h2 id="bytecode-dissected">Bytecode dissected</h2> | |
| <p>What exactly is in the bytecode? Here is the hexdump of <code>hello.luac</code> (made by <code>hd</code> on my system).</p> | |
| <pre><code>00000000 1b 4c 75 61 52 00 01 04 04 04 08 00 19 93 0d 0a |.LuaR...........| | |
| 00000010 1a 0a 00 00 00 00 00 00 00 00 00 01 04 07 00 00 |................| | |
| 00000020 00 01 00 00 00 46 40 40 00 80 00 00 00 c1 80 00 |.....F@@........| | |
| 00000030 00 96 c0 00 01 5d 40 00 01 1f 00 80 00 03 00 00 |.....]@.........| | |
| 00000040 00 04 06 00 00 00 48 65 6c 6c 6f 00 04 06 00 00 |......Hello.....| | |
| 00000050 00 70 72 69 6e 74 00 04 08 00 00 00 20 57 6f 72 |.print...... Wor| | |
| 00000060 6c 64 21 00 00 00 00 00 01 00 00 00 01 00 30 00 |ld!...........0.| | |
| 00000070 00 00 6c 6f 63 61 6c 20 20 68 65 6c 6c 6f 20 3d |..local hello =| | |
| 00000080 20 22 48 65 6c 6c 6f 22 20 70 72 69 6e 74 20 28 | "Hello" print (| | |
| 00000090 68 65 6c 6c 6f 2e 2e 22 20 57 6f 72 6c 64 21 22 |hello.." World!"| | |
| 000000a0 29 00 07 00 00 00 01 00 00 00 01 00 00 00 01 00 |)...............| | |
| 000000b0 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 |................| | |
| 000000c0 00 00 01 00 00 00 06 00 00 00 68 65 6c 6c 6f 00 |..........hello.| | |
| 000000d0 01 00 00 00 07 00 00 00 01 00 00 00 05 00 00 00 |................| | |
| 000000e0 5f 45 4e 56 00 |_ENV.|</code></pre> | |
| <p>Much of that is for debugging, so pass <code>hello.luac</code> through <code>luac -s</code> and try again.</p> | |
| <pre><code>00000000 1b 4c 75 61 52 00 01 04 04 04 08 00 19 93 0d 0a |.LuaR...........| | |
| 00000010 1a 0a 00 00 00 00 00 00 00 00 00 01 04 07 00 00 |................| | |
| 00000020 00 01 00 00 00 46 40 40 00 80 00 00 00 c1 80 00 |.....F@@........| | |
| 00000030 00 96 c0 00 01 5d 40 00 01 1f 00 80 00 03 00 00 |.....]@.........| | |
| 00000040 00 04 06 00 00 00 48 65 6c 6c 6f 00 04 06 00 00 |......Hello.....| | |
| 00000050 00 70 72 69 6e 74 00 04 08 00 00 00 20 57 6f 72 |.print...... Wor| | |
| 00000060 6c 64 21 00 00 00 00 00 01 00 00 00 01 00 00 00 |ld!.............| | |
| 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |..............|</code></pre> | |
| <p>The format is not officially documented, and needs to be reverse-engineered. The necessary material is in the Lua source code, of course, in several places, mainly <code>ldump.c</code> and <code>lundump.c</code>. I have also cross-checked with NFI and LAT, but any remaining errors are mine.</p> | |
| <p>The code starts with an 18-byte file header, which is the same for all official Lua 5.2 bytecode compiled on a machine like yours, whether by <code>luac</code> or <code>load</code> or <code>loadfile</code>. Lua 5.1 only had a 12-byte header, similar to the first 12 bytes of this one.</p> | |
| <p>Byte numbers are in origin-1 decimal (mostly showing the arithmetic) and origin-0 hex.</p> | |
| <dl> | |
| <dt><code>1 x00: 1b 4c 75 61</code></dt> | |
| <dd><code>LUA_SIGNATURE</code> from <code>lua.h</code>. | |
| </dd> | |
| <dt><code>5 x04: 52 00</code></dt> | |
| <dd>Binary-coded decimal 52 for the Lua version, <code>00</code> to say the bytecode is compatible with the "official" PUC-Rio implementation. | |
| </dd> | |
| <dt><code>5+2 x06: 01 04 04 04 08 00</code></dt> | |
| <dd>Six system parameters. On x386 machines they mean: little-endian, 4-byte integers, 4-byte VM instructions, 4-byte <code>size_t</code> numbers, 8-byte Lua numbers, floating-point. These parameters must all match up between the bytecode file and the Lua interpreter, otherwise the bytecode is invalid. | |
| </dd> | |
| <dt><code>7+6 x0c: 19 93 0d 0a 1a 0a</code></dt> | |
| <dd>Present in all bytecode produced by Lua 5.2 from PUC-Rio. Described in <code>lundump.h</code> as "data to catch conversion errors". Might be constructed from binary-coded decimal 1993 (the year it all started), Windows line terminator, MS-DOS text file terminator, Unix line terminator. | |
| </dd> | |
| </dl> | |
| <p>After these 18 bytes come the functions defined in the file. Each function starts with an 11-byte function header.</p> | |
| <dl> | |
| <dt><code>13+6 x12: 00 00 00 00</code></dt> | |
| <dd>Line number in source code where chunk starts. 0 for the main chunk. | |
| </dd> | |
| <dt><code>19+4 x16: 00 00 00 00</code></dt> | |
| <dd>Line number in source code where chunk stops. 0 for the main chunk. | |
| </dd> | |
| <dt><code>23+4 x1a: 00 01 04</code></dt> | |
| <dd>Number of parameters, vararg flag, number of registers used by this function (not more than 255, obviously). Local variables are stored in registers; there may not be more than 200 of them (see <code>lparser.c</code>). | |
| </dd> | |
| </dl> | |
| <p>Then a list of instructions. We'll do their format in more detail later.</p> | |
| <dl> | |
| <dt><code>27+3 x1d: 07 00 00 00</code></dt> | |
| <dd>There are 7 instructions in the list (little-endian byte order, as specified in the file header).<br /> | |
| </dd> | |
| <dt><code>30+4*1 x21: 01 00 00 00</code></dt> | |
| <dd>Bytecode for <code>LOADK 0 -1</code>. | |
| </dd> | |
| <dt><code>30+4*2 x25: 46 40 40 00</code></dt> | |
| <dd>Bytecode for <code>GETTABUP 1 0 -2</code>. | |
| </dd> | |
| <dt><code>30+4*3 x29: 80 00 00 00</code></dt> | |
| <dd>Bytecode for <code>MOVE 2 0</code>. | |
| </dd> | |
| <dt><code>30+4*4 x2d: c1 80 00 00</code></dt> | |
| <dd>Bytecode for <code>LOADK 3 -3</code>. | |
| </dd> | |
| <dt><code>30+4*5 x31: 96 c0 00 01</code></dt> | |
| <dd>Bytecode for <code>CONCAT 2 2 3</code>. | |
| </dd> | |
| <dt><code>30+4*6 x35: 5d 40 00 01</code></dt> | |
| <dd>Bytecode for <code>CALL 1 2 1</code>. | |
| </dd> | |
| <dt><code>30+4*7 x39: 1f 00 80 00</code></dt> | |
| <dd>Bytecode for <code>RETURN 0 1</code>. | |
| </dd> | |
| </dl> | |
| <p>Next a list of constants.</p> | |
| <dl> | |
| <dt><code>58+4 x3d: 03 00 00 00</code></dt> | |
| <dd>There are 3 constants. | |
| </dd> | |
| <dt><code>62+4 x41: 04</code></dt> | |
| <dd>First constant is a string, | |
| </dd> | |
| <dt><code>66+1 x42: 06 00 00 00</code></dt> | |
| <dd>with 6 bytes in it, | |
| </dd> | |
| <dt><code>67+4 x46: 48 65 6c 6c 6f 00</code></dt> | |
| <dd>containing "Hello" and a terminating zero byte. | |
| </dd> | |
| <dt><code>71+6 x4c: 04 06 00 00 00 70 72 69 6e 74 00</code></dt> | |
| <dd>Second constant is the 6-byte string "print". | |
| </dd> | |
| <dt><code>77+5+6 x57: 04 08 00 00 00 20 57 6f 72 6c 64 21 00</code></dt> | |
| <dd>Third constant is the 8-byte string " World!" | |
| </dd> | |
| </dl> | |
| <p>Followed by a list of function prototypes.</p> | |
| <dl> | |
| <dt><code>88+5+8 x64: 00 00 00 00</code></dt> | |
| <dd>There are no function prototypes. | |
| </dd> | |
| </dl> | |
| <p>Finally followed by a list of upvalues.</p> | |
| <dl> | |
| <dt><code>101+4 x68: 01 00 00 00</code></dt> | |
| <dd>There is 1 upvalue. | |
| </dd> | |
| <dt><code>105+4 x6c: 01 00</code></dt> | |
| <dd>It can be found in a stack 1 level up, at position 0 in that stack. | |
| </dd> | |
| </dl> | |
| <p>At this point, the two files differ. In the stripped version, the absence of debugging information shows as 16 zero bytes.</p> | |
| <dl> | |
| <dt><code>109+2 x6e: 00 00 00 00</code></dt> | |
| <dd>There is no source code | |
| </dd> | |
| <dt><code>111+4 x72: 00 00 00 00</code></dt> | |
| <dd>The list of line numbers is empty | |
| </dd> | |
| <dt><code>115+4 x76: 00 00 00 00</code></dt> | |
| <dd>The list of local variable names is empty | |
| </dd> | |
| <dt><code>119+4 x7a: 00 00 00 00</code></dt> | |
| <dd>The list of upvalue names is empty | |
| </dd> | |
| </dl> | |
| <p>In the unstripped version, the information is given thus:</p> | |
| <dl> | |
| <dt><code>111 x6e: 30 00 00 00</code></dt> | |
| <dd>The source code is 48 bytes long. Skipping past the source, | |
| </dd> | |
| <dt><code>111+4+48 xa2: 07 00 00 00</code></dt> | |
| <dd>There are seven line numbers. Skipping past those numbers, | |
| </dd> | |
| <dt><code>163+4+28 xc2: 01 00 00 00</code></dt> | |
| <dd>One local variable name is listed. | |
| </dd> | |
| <dt><code>199 xc6: 06 00 00 00 68 65 6c 6c 6f 00</code></dt> | |
| <dd>It's <code>hello</code>. | |
| </dd> | |
| <dt><code>199+4+6 xd0: 01 00 00 00</code></dt> | |
| <dd>It comes into scope at instruction 1. | |
| </dd> | |
| <dt><code>209+4 xd4: 07 00 00 00</code></dt> | |
| <dd>It goes out of scope at instruction 7. | |
| </dd> | |
| <dt><code>213+4 xd8: 01 00 00 00</code></dt> | |
| <dd>One upvalue name is listed. | |
| </dd> | |
| <dt><code>217+4 xdc: 05 00 00 00 5f 45 4e 56 00</code></dt> | |
| <dd>It's <code>_ENV</code>. | |
| </dd> | |
| </dl> | |
| <p>After this, more functions may be encoded in the same way.</p> | |
| <p>That's as far as I go. The following areas have not been covered:</p> | |
| <ul> | |
| <li>How non-string constants are stored.</li> | |
| <li>How function prototypes look.</li> | |
| </ul> | |
| <p>NFI has all that and more, but it's 5.1 only, so you will have to check with LAT and/or <code>lundump.c</code>.</p> | |
| <h2 id="snooping-around-in-the-bytecode">Snooping around in the bytecode</h2> | |
| <p>This document is accompanied by two module files: <code>vm52.lua</code> and <code>bytecode.lua</code>. From the first, we use only <code>numberAt</code> at this stage. This function converts a four-byte substring to a 32-bit number, taking into account the endian-ness of the host system. We know from the above that a Lua 5.2 dumped function has the varargs flag at 28, the register count at 29, and the instruction count at position 30, followed by that many four-byte instructions.</p> | |
| <p>So let us take a look.</p> | |
| <pre><code>$ lua -l vm52 | |
| Lua 5.2.1 Copyright (C) 1994-2012 Lua.org, PUC-Rio | |
| func = load 'local hello = "Hello" print (hello.." World!")' | |
| chunk = string.dump(func) | |
| function hasvarargs(chunk) return chunk:sub(28,28):byte()>0 end | |
| function nstack(chunk) return chunk:sub(29,29):byte() end | |
| function ninstr(chunk) return vm52.numberAt(chunk,30) end | |
| function instr(chunk,i) return vm52.numberAt(chunk:sub(30+4*i,33+4*i)) end | |
| ns = nstack(chunk) | |
| print ("This function" .. | |
| (hasvarargs(chunk) and " has a variable number of arguments and" or "") .. | |
| " uses "..ns.." register"..(ns~=1 and "s" or "")) | |
| for i=1,ninstr(chunk) do | |
| print(string.format("%08X",instr(chunk,i))) | |
| end | |
| 00000001 | |
| 00404046 | |
| 00000080 | |
| 000080C1 | |
| 0100C096 | |
| 0100405D | |
| 0080001F</code></pre> | |
| <p>You can see the bytes of the bytecode file, reversed because my machine is little-endian.</p> | |
| <p>The module file <code>vm52.lua</code> has more than just <code>numberAt</code>: it has the four functions I defined in the above code, and more important, it has <code>assemble</code> and <code>disassemble</code>. The latter does much the same as <code>luac -l</code> does when given dumped bytecode. Following on the previous Lua session,</p> | |
| <pre><code> for i=1,vm52.ninstr(code) do | |
| print(vm52.disassemble(vm52.instr(code,i))) | |
| end | |
| LOADK 0 -1 | |
| GETTABUP 1 0 -2 | |
| MOVE 2 0 | |
| LOADK 3 -3 | |
| CONCAT 2 2 3 | |
| CALL 1 2 1 | |
| RETURN 0 1</code></pre> | |
| <h2 id="instruction-anatomy">Instruction anatomy</h2> | |
| <p>The above hex listing corresponds exactly with the bytecode file, except that the bytes are reversed. This is a consequence of the little endian-ness of my system: little-endian byte order is really hard to read when you work with 32-bit numbers, given that bytes are universally coded with big-endian bits.</p> | |
| <p>So the instruction bytes need to be reversed before we decompose them into bits and display them. The <code>numberAt</code> function does that if necessary.</p> | |
| <p>As we have seen, the instructions are:</p> | |
| <pre><code>00 00 00 01 LOADK 0 -1 | |
| 00 40 40 46 GETTABUP 1 0 -2 | |
| 00 00 00 80 MOVE 2 0 | |
| 00 00 80 c1 LOADK 3 -3 | |
| 01 00 c0 96 CONCAT 2 2 3 | |
| 01 00 40 5d CALL 1 2 1 | |
| 00 80 00 1f RETURN 0 1</code></pre> | |
| <p>We now expand the bytes into bits and group them. If <code>lopcodes.c</code> says the instruction has mode <code>iABC</code>, we group the bits as (9,9,8,6), defining the values <code>B</code>,<code>C</code>,<code>A</code>,<code>OP</code>; if it says <code>iABx</code> or <code>iAsBx</code>, as (18,8,6), defining the values <code>Bx</code>,<code>A</code>,<code>OP</code> or <code>sBx</code>,<code>A</code>,<code>OP</code>; if it says <code>iAx</code>, as (26,6), defining the values <code>Ax</code>,<code>OP</code>.</p> | |
| <pre><code> B Bx C A OP | |
| 000000000000000000 00000000 000001 LOADK 0 -1 (A,Bx) | |
| 000000000 100000001 00000001 000110 GETTABUP 1 0 -2 (A,B,C) | |
| 000000000 000000000 00000010 000000 MOVE 2 0 (A,B) | |
| 000000000000000010 00000011 000001 LOADK 3 -3 (A,Bx) | |
| 000000010 000000011 00000010 010110 CONCAT 2 2 3 (A,B,C) | |
| 000000010 000000001 00000001 011101 CALL 1 2 1 (A,B,C) | |
| 000000001 000000000 00000000 011111 RETURN 0 1 (A,B)</code></pre> | |
| <p>In the above table, the bits are numbered left to right as 31, 30, …, 0. The notation "0+6" means the 6 bits with low-order bit (rightmost in the above table) at position 0. If the instruction is stored as a Lua number, these bits are given by <code>bit32:extract(0,6)</code>.</p> | |
| <ol style="list-style-type: decimal"> | |
| <li><code>OP</code> is the opcode, at 0+6 (see below).</li> | |
| <li><code>A</code> is the first operand in all instructions, at 6:8, treated as an unsigned number. Its maximum value is 255.</li> | |
| <li><p><code>B</code> is the second operand in instructions with mode <code>iABC</code>, at 23+9, treated as a signed number. The actual instruction does not always use <code>B</code>.</p> | |
| The first bit is the sign bit. If this is 1, the other eight bits encode one less than the absolute value of <code>B</code>. Thus there is no -0, the minimum value of <code>B</code> is -256 and its maximum value is 255.</li> | |
| <li><code>C</code> is the third operand in instructions with mode <code>iABC</code>, at 14+9, treated as a signed number like <code>B</code>. The actual instruction does not always use <code>C</code>.</li> | |
| <li><code>Bx</code> is the second operand in instructions with mode <code>iABx</code>, at 14+18, treated as a 19-bit signed number like <code>B</code> but not storing the sign bit since <code>Bx</code> is always negative.</li> | |
| <li><code>sBx</code> means "<code>Bx</code> treated as an 18-bit signed number."</li> | |
| <li><p><code>Ax</code> is the only operand in instructions with mode <code>iAx</code>, at 6+26, treated like <code>Bx</code> (27-bit signed number, always negative, sign bit not stored).</p></li> | |
| </ol> | |
| <p>The opcodes and a brief description of what the instructions do are given as an <code>enum</code> in <code>lopcodes.h</code>. We have grouped them in groups of related functionality. The description (taken verbatim from <code>lopcodes.h</code>) uses the following notation, in which <code>B</code>, <code>Bx</code> etc refer not to numbers encoded in bits, but to the numbers visible in the disassembly shown by <code>luac</code>.</p> | |
| <dl> | |
| <dt><code>R(A)</code>, <code>R(B)</code>, <code>R(C)</code></dt> | |
| <dd>The register numbered <code>A</code>, <code>B</code> or <code>C</code>. | |
| </dd> | |
| <dt><code>Kst(Bx)</code></dt> | |
| <dd>The constant numbered <code>|Bx|.</code> | |
| </dd> | |
| <dt><code>KPROTO[Bx]</code></dt> | |
| <dd>The function prototype numbered <code>|Bx|</code>. | |
| </dd> | |
| <dt><code>RK(B)</code>, <code>RK(C)</code></dt> | |
| <dd>The register numbered <code>B</code> or <code>C</code>; if <code>B</code> or <code>C</code> is negative, the constant numbered <code>|B|</code> or <code>|C|</code>. | |
| </dd> | |
| <dt><code>FPF</code></dt> | |
| <dd>Fields per flush. Read NFI's explanation. | |
| </dd> | |
| <dt><code>UpValue[B]</code></dt> | |
| <dd>The upvalue numbered <code>B</code>. | |
| </dd> | |
| <dt><code>pc</code></dt> | |
| <dd>Program counter: which instruction will be executed next. | |
| </dd> | |
| <dt><code>closure</code></dt> | |
| <dd>Make a closure (function plus upvalues). | |
| </dd> | |
| </dl> | |
| <h3 id="loading-constants">Loading constants</h3> | |
| <table> | |
| <thead> | |
| <tr class="header"> | |
| <th align="right">opcode</th> | |
| <th align="left">name and args</th> | |
| <th align="left">description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="odd"> | |
| <td align="right">1</td> | |
| <td align="left">LOADK(A,Bx)</td> | |
| <td align="left">R(A) := Kst(Bx)</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">2</td> | |
| <td align="left">LOADKX(A)</td> | |
| <td align="left">R(A) := Kst(extra arg)</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">39</td> | |
| <td align="left">EXTRAARG(Ax)</td> | |
| <td align="left">extra (larger) argument for previous opcode</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <h3 id="unary-functions">Unary functions</h3> | |
| <table> | |
| <thead> | |
| <tr class="header"> | |
| <th align="right">opcode</th> | |
| <th align="left">name and args</th> | |
| <th align="left">description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="odd"> | |
| <td align="right">0</td> | |
| <td align="left">MOVE(A,B)</td> | |
| <td align="left">R(A) := R(B)</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">19</td> | |
| <td align="left">UNM(A,B)</td> | |
| <td align="left">R(A) := -R(B)</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">20</td> | |
| <td align="left">NOT(A,B)</td> | |
| <td align="left">R(A) := not R(B)</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">21</td> | |
| <td align="left">LEN(A,B)</td> | |
| <td align="left">R(A) := length of R(B)</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <h3 id="binary-functions">Binary functions</h3> | |
| <table> | |
| <thead> | |
| <tr class="header"> | |
| <th align="right">opcode</th> | |
| <th align="left">name and args</th> | |
| <th align="left">description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="odd"> | |
| <td align="right">13</td> | |
| <td align="left">ADD(A,B,C)</td> | |
| <td align="left">R(A) := RK(B) + RK(C)</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">14</td> | |
| <td align="left">SUB(A,B,C)</td> | |
| <td align="left">R(A) := RK(B) - RK(C)</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">15</td> | |
| <td align="left">MUL(A,B,C)</td> | |
| <td align="left">R(A) := RK(B) * RK(C)</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">16</td> | |
| <td align="left">DIV(A,B,C)</td> | |
| <td align="left">R(A) := RK(B) / RK(C)</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">17</td> | |
| <td align="left">MOD(A,B,C)</td> | |
| <td align="left">R(A) := RK(B) % RK(C)</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">18</td> | |
| <td align="left">POW(A,B,C)</td> | |
| <td align="left">R(A) := RK(B) ^ RK(C)</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <h3 id="table-access">Table access</h3> | |
| <table> | |
| <thead> | |
| <tr class="header"> | |
| <th align="right">opcode</th> | |
| <th align="left">name and args</th> | |
| <th align="left">description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="odd"> | |
| <td align="right">7</td> | |
| <td align="left">GETTABLE(A,B,C)</td> | |
| <td align="left">R(A) := R(B)[RK(C)]</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">10</td> | |
| <td align="left">SETTABLE(A,B,C)</td> | |
| <td align="left">R(A)[RK(B)] := RK(C)</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">11</td> | |
| <td align="left">NEWTABLE(A,B,C)</td> | |
| <td align="left">R(A) := {} (size = B,C)</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">12</td> | |
| <td align="left">SELF(A,B,C)</td> | |
| <td align="left">R(A+1) := R(B); R(A) := R(B)[RK(C)]</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">36</td> | |
| <td align="left">SETLIST(A,B,C)</td> | |
| <td align="left">R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B)</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <h3 id="dealing-with-tuples">Dealing with tuples</h3> | |
| <table> | |
| <thead> | |
| <tr class="header"> | |
| <th align="right">opcode</th> | |
| <th align="left">name and args</th> | |
| <th align="left">description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="odd"> | |
| <td align="right">4</td> | |
| <td align="left">LOADNIL(A,B)</td> | |
| <td align="left">R(A), R(A+1), ..., R(A+B) := nil</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">22</td> | |
| <td align="left">CONCAT(A,B,C)</td> | |
| <td align="left">R(A) := R(B).. ... ..R(C)</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">29</td> | |
| <td align="left">CALL(A,B,C)</td> | |
| <td align="left">R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">31</td> | |
| <td align="left">RETURN(A,B)</td> | |
| <td align="left">return R(A), ... ,R(A+B-2) (see note)</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">38</td> | |
| <td align="left">VARARG(A,B)</td> | |
| <td align="left">R(A), R(A+1), ..., R(A+B-2) = vararg</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">30</td> | |
| <td align="left">TAILCALL(A,B,C)</td> | |
| <td align="left">return R(A)(R(A+1), ... ,R(A+B-1))</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">34</td> | |
| <td align="left">TFORCALL(A,C)</td> | |
| <td align="left">R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <h3 id="interaction-with-upvalues">Interaction with upvalues</h3> | |
| <table> | |
| <thead> | |
| <tr class="header"> | |
| <th align="right">opcode</th> | |
| <th align="left">name and args</th> | |
| <th align="left">description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="odd"> | |
| <td align="right">5</td> | |
| <td align="left">GETUPVAL(A,B)</td> | |
| <td align="left">R(A) := UpValue[B]</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">9</td> | |
| <td align="left">SETUPVAL(A,B)</td> | |
| <td align="left">UpValue[B] := R(A)</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">6</td> | |
| <td align="left">GETTABUP(A,B,C)</td> | |
| <td align="left">R(A) := UpValue[B][RK(C)]</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">8</td> | |
| <td align="left">SETTABUP(A,B,C)</td> | |
| <td align="left">UpValue[A][RK(B)] := RK(C)</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <h3 id="logical-functions">Logical functions</h3> | |
| <table> | |
| <thead> | |
| <tr class="header"> | |
| <th align="right">opcode</th> | |
| <th align="left">name and args</th> | |
| <th align="left">description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="odd"> | |
| <td align="right">3</td> | |
| <td align="left">LOADBOOL(A,B,C)</td> | |
| <td align="left">R(A) := (Bool)B; if (C) pc++</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">24</td> | |
| <td align="left">EQ(A,B,C)</td> | |
| <td align="left">if ((RK(B) == RK(C)) ~= A) then pc++</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">25</td> | |
| <td align="left">LT(A,B,C)</td> | |
| <td align="left">if ((RK(B) < RK(C)) ~= A) then pc++</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">26</td> | |
| <td align="left">LE(A,B,C)</td> | |
| <td align="left">if ((RK(B) <= RK(C)) ~= A) then pc++</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">27</td> | |
| <td align="left">TEST(A,C)</td> | |
| <td align="left">if not (R(A) <=> C) then pc++</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">28</td> | |
| <td align="left">TESTSET(A,B,C)</td> | |
| <td align="left">if (R(B) <=> C) then R(A) := R(B) else pc++</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <h3 id="branches-loops-and-closures">Branches, loops and closures</h3> | |
| <table> | |
| <thead> | |
| <tr class="header"> | |
| <th align="right">opcode</th> | |
| <th align="left">name and args</th> | |
| <th align="left">description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="odd"> | |
| <td align="right">23</td> | |
| <td align="left">JMP(A,sBx)</td> | |
| <td align="left">pc+=sBx; if (A) close all upvalues >= R(A) + 1</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">32</td> | |
| <td align="left">FORLOOP(A,sBx)</td> | |
| <td align="left">R(A)+=R(A+2); if R(A) <?= R(A+1) then</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right"></td> | |
| <td align="left"></td> | |
| <td align="left">{ pc+=sBx; R(A+3)=R(A) }</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right">33</td> | |
| <td align="left">FORPREP(A,sBx)</td> | |
| <td align="left">R(A)-=R(A+2); pc+=sBx</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">35</td> | |
| <td align="left">TFORLOOP(A,sBx)</td> | |
| <td align="left">if R(A+1) ~= nil then</td> | |
| </tr> | |
| <tr class="even"> | |
| <td align="right"></td> | |
| <td align="left"></td> | |
| <td align="left">{ R(A)=R(A+1); pc += sBx }</td> | |
| </tr> | |
| <tr class="odd"> | |
| <td align="right">37</td> | |
| <td align="left">CLOSURE(A,Bx)</td> | |
| <td align="left">R(A) := closure(KPROTO[Bx])</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <h3 id="notes">Notes</h3> | |
| <p>Taken nearly verbatim from <code>lopcodes.h</code>.</p> | |
| <ul> | |
| <li><p>In CALL, if (<code>B == 0</code>) then <code>B = top</code>. If (<code>C == 0</code>), then <code>top</code> is set to <code>last_result+1</code>, so next open instruction (CALL, RETURN, SETLIST) may use <code>top</code>.</p></li> | |
| <li><p>In VARARG, if (<code>B == 0</code>) then use actual number of varargs and set top (like in CALL with <code>C == 0</code>).</p></li> | |
| <li><p>In RETURN, if (<code>B == 0</code>) then return up to <code>top</code>.</p></li> | |
| <li><p>In SETLIST, if (<code>B == 0</code>) then <code>B =</code>top<code>; if (</code>C == 0`) then next 'instruction' is EXTRAARG(real C).</p></li> | |
| <li><p>In LOADKX, the next 'instruction' is always EXTRAARG.</p></li> | |
| <li><p>For comparisons, A specifies what condition the test should accept (true or false).</p></li> | |
| <li><p>All 'skips' (<code>pc++</code>) assume that next instruction is a jump.</p></li> | |
| </ul> | |
| <h2 id="very-brief-remarks-on-vm5.2">Very brief remarks on VM5.2</h2> | |
| <blockquote> | |
| <p>A little learning is a dangerous thing.<br />Drink deep, or taste not the Pierian spring.</p> | |
| </blockquote> | |
| <p>Those well-known words of Alexander Pope serve as a warning here.</p> | |
| <p>You should really read NFI first and treat this section as a mere summary of the points needed to understand the rest of this document. Actually, it contains only the stuff I had to look up for myself after reading the description of the operations above, so it may be a good idea to re-read that before continuing.</p> | |
| <p>I found it helpful to disassemble short snippets of Lua code and figure out why the VM instructions achieve the desired result. For example:</p> | |
| <pre><code>$ lua -l vm52 | |
| src = "local x, y = ...; return x+y, ..." | |
| chunk = string.dump(load(src)) | |
| for i=1,vm52.ninstr(chunk) do | |
| print(vm52.disassemble(vm52.instr(chunk,i))) | |
| end | |
| VARARG 0 3 | |
| ADD 2 0 1 | |
| VARARG 3 0 | |
| RETURN 2 0 | |
| RETURN 0 1 </code></pre> | |
| <dl> | |
| <dt><code>VARARG 0 3</code></dt> | |
| <dd>Load the first (3-1) arguments from the vararg list into consecutive registers starting at register 0. | |
| </dd> | |
| <dt><code>ADD 2 0 1</code></dt> | |
| <dd>Add registers 0 and 1, storing the result in register 2. | |
| </dd> | |
| <dt><code>VARARG 3 0</code></dt> | |
| <dd>Load all of the vararg list into consecutive registers starting at register 3. | |
| </dd> | |
| <dt><code>RETURN 2 0</code></dt> | |
| <dd>Return everything from register 2 to the stack top. | |
| </dd> | |
| <dt><code>RETURN 0 1</code></dt> | |
| <dd>Lua can't be sure in general that there is no condition branch skipping the preceding return statement and always inserts another return statement for good measure. | |
| </dd> | |
| </dl> | |
| <p>The C API of Lua does everything on a virtual stack. Seen in a certain way, the virtual machine is merely an interface to the API, providing a layer of abstraction between Lua source code and the API. However, the VM has access to the private fields of a <code>lua_State</code>, whereas the C API works entirely via the documented routines.</p> | |
| <p>The Virtual Machine itself is not stack-based. It is allowed exclusive use of a portion of the stack called its <em>frame</em>, but within that frame, the VM works with constant register numbers: there is no pushing and popping. An important difference in usage is that negative indices don't mean the same as in the C API. They refer to constants stored elsewhere, not in the stack frame.</p> | |
| <p>The VM can access items on other active stack frames that have been provided as the upvalues.</p> | |
| <p>Local variables are aliases for registers. The <code>luac</code> listing grabs their names from the debugging information, but instruction disassembly by itself cannot do that. There is no overhead involved in creating them.</p> | |
| <h2 id="thats-it-for-now">That's it for now</h2> | |
| <p>When I have time and inclination to add more, I'll do so. Please send corrections and comments. If I am still active and about, you will find me on <code>lua-l</code>.</p> | |
| </body> | |
| </html> |