xref: /php-src/ext/opcache/jit/ir/dynasm/dynasm.lua (revision 2ab1c3d5)
1------------------------------------------------------------------------------
2-- DynASM. A dynamic assembler for code generation engines.
3-- Originally designed and implemented for LuaJIT.
4--
5-- Copyright (C) 2005-2023 Mike Pall. All rights reserved.
6-- See below for full copyright notice.
7------------------------------------------------------------------------------
8
9-- Application information.
10local _info = {
11  name =	"DynASM",
12  description =	"A dynamic assembler for code generation engines",
13  version =	"1.5.0",
14  vernum =	 10500,
15  release =	"2021-05-02",
16  author =	"Mike Pall",
17  url =		"https://luajit.org/dynasm.html",
18  license =	"MIT",
19  copyright =	[[
20Copyright (C) 2005-2023 Mike Pall. All rights reserved.
21
22Permission is hereby granted, free of charge, to any person obtaining
23a copy of this software and associated documentation files (the
24"Software"), to deal in the Software without restriction, including
25without limitation the rights to use, copy, modify, merge, publish,
26distribute, sublicense, and/or sell copies of the Software, and to
27permit persons to whom the Software is furnished to do so, subject to
28the following conditions:
29
30The above copyright notice and this permission notice shall be
31included in all copies or substantial portions of the Software.
32
33THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
34EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
36IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
37CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
38TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
39SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40
41[ MIT license: https://www.opensource.org/licenses/mit-license.php ]
42]],
43}
44
45-- Cache library functions.
46local type, pairs, ipairs = type, pairs, ipairs
47local pcall, error, assert = pcall, error, assert
48local _s = string
49local sub, match, gmatch, gsub = _s.sub, _s.match, _s.gmatch, _s.gsub
50local format, rep, upper = _s.format, _s.rep, _s.upper
51local _t = table
52local insert, remove, concat, sort = _t.insert, _t.remove, _t.concat, _t.sort
53local exit = os.exit
54local io = io
55local stdin, stdout, stderr = io.stdin, io.stdout, io.stderr
56
57------------------------------------------------------------------------------
58
59-- Program options.
60local g_opt = {}
61
62-- Global state for current file.
63local g_fname, g_curline, g_indent, g_lineno, g_synclineno, g_arch
64local g_errcount = 0
65
66-- Write buffer for output file.
67local g_wbuffer, g_capbuffer
68
69------------------------------------------------------------------------------
70
71-- Write an output line (or callback function) to the buffer.
72local function wline(line, needindent)
73  local buf = g_capbuffer or g_wbuffer
74  buf[#buf+1] = needindent and g_indent..line or line
75  g_synclineno = g_synclineno + 1
76end
77
78-- Write assembler line as a comment, if requested.
79local function wcomment(aline)
80  if g_opt.comment then
81    wline(g_opt.comment..aline..g_opt.endcomment, true)
82  end
83end
84
85-- Resync CPP line numbers.
86local function wsync()
87  if g_synclineno ~= g_lineno and g_opt.cpp then
88    wline("#line "..g_lineno..' "'..g_fname..'"')
89    g_synclineno = g_lineno
90  end
91end
92
93-- Dummy action flush function. Replaced with arch-specific function later.
94local function wflush(term)
95end
96
97-- Dump all buffered output lines.
98local function wdumplines(out, buf)
99  for _,line in ipairs(buf) do
100    if type(line) == "string" then
101      assert(out:write(line, "\n"))
102    else
103      -- Special callback to dynamically insert lines after end of processing.
104      line(out)
105    end
106  end
107end
108
109------------------------------------------------------------------------------
110
111-- Emit an error. Processing continues with next statement.
112local function werror(msg)
113  error(format("%s:%s: error: %s:\n%s", g_fname, g_lineno, msg, g_curline), 0)
114end
115
116-- Emit a fatal error. Processing stops.
117local function wfatal(msg)
118  g_errcount = "fatal"
119  werror(msg)
120end
121
122-- Print a warning. Processing continues.
123local function wwarn(msg)
124  stderr:write(format("%s:%s: warning: %s:\n%s\n",
125    g_fname, g_lineno, msg, g_curline))
126end
127
128-- Print caught error message. But suppress excessive errors.
129local function wprinterr(...)
130  if type(g_errcount) == "number" then
131    -- Regular error.
132    g_errcount = g_errcount + 1
133    if g_errcount < 21 then -- Seems to be a reasonable limit.
134      stderr:write(...)
135    elseif g_errcount == 21 then
136      stderr:write(g_fname,
137	":*: warning: too many errors (suppressed further messages).\n")
138    end
139  else
140    -- Fatal error.
141    stderr:write(...)
142    return true -- Stop processing.
143  end
144end
145
146------------------------------------------------------------------------------
147
148-- Map holding all option handlers.
149local opt_map = {}
150local opt_current
151
152-- Print error and exit with error status.
153local function opterror(...)
154  stderr:write("dynasm.lua: ERROR: ", ...)
155  stderr:write("\n")
156  exit(1)
157end
158
159-- Get option parameter.
160local function optparam(args)
161  local argn = args.argn
162  local p = args[argn]
163  if not p then
164    opterror("missing parameter for option `", opt_current, "'.")
165  end
166  args.argn = argn + 1
167  return p
168end
169
170------------------------------------------------------------------------------
171
172-- Core pseudo-opcodes.
173local map_coreop = {}
174-- Dummy opcode map. Replaced by arch-specific map.
175local map_op = {}
176
177-- Forward declarations.
178local dostmt
179local readfile
180
181------------------------------------------------------------------------------
182
183-- Map for defines (initially empty, chains to arch-specific map).
184local map_def = {}
185
186-- Pseudo-opcode to define a substitution.
187map_coreop[".define_2"] = function(params, nparams)
188  if not params then return nparams == 1 and "name" or "name, subst" end
189  local name, def = params[1], params[2] or "1"
190  if not match(name, "^[%a_][%w_]*$") then werror("bad or duplicate define") end
191  map_def[name] = def
192end
193map_coreop[".define_1"] = map_coreop[".define_2"]
194
195-- Define a substitution on the command line.
196function opt_map.D(args)
197  local namesubst = optparam(args)
198  local name, subst = match(namesubst, "^([%a_][%w_]*)=(.*)$")
199  if name then
200    map_def[name] = subst
201  elseif match(namesubst, "^[%a_][%w_]*$") then
202    map_def[namesubst] = "1"
203  else
204    opterror("bad define")
205  end
206end
207
208-- Undefine a substitution on the command line.
209function opt_map.U(args)
210  local name = optparam(args)
211  if match(name, "^[%a_][%w_]*$") then
212    map_def[name] = nil
213  else
214    opterror("bad define")
215  end
216end
217
218-- Helper for definesubst.
219local gotsubst
220
221local function definesubst_one(word)
222  local subst = map_def[word]
223  if subst then gotsubst = word; return subst else return word end
224end
225
226-- Iteratively substitute defines.
227local function definesubst(stmt)
228  -- Limit number of iterations.
229  for i=1,100 do
230    gotsubst = false
231    stmt = gsub(stmt, "#?[%w_]+", definesubst_one)
232    if not gotsubst then break end
233  end
234  if gotsubst then wfatal("recursive define involving `"..gotsubst.."'") end
235  return stmt
236end
237
238-- Dump all defines.
239local function dumpdefines(out, lvl)
240  local t = {}
241  for name in pairs(map_def) do
242    t[#t+1] = name
243  end
244  sort(t)
245  out:write("Defines:\n")
246  for _,name in ipairs(t) do
247    local subst = map_def[name]
248    if g_arch then subst = g_arch.revdef(subst) end
249    out:write(format("  %-20s %s\n", name, subst))
250  end
251  out:write("\n")
252end
253
254------------------------------------------------------------------------------
255
256-- Support variables for conditional assembly.
257local condlevel = 0
258local condstack = {}
259
260-- Evaluate condition with a Lua expression. Substitutions already performed.
261local function cond_eval(cond)
262  local func, err
263  if setfenv then
264    func, err = loadstring("return "..cond, "=expr")
265  else
266    -- No globals. All unknown identifiers evaluate to nil.
267    func, err = load("return "..cond, "=expr", "t", {})
268  end
269  if func then
270    if setfenv then
271      setfenv(func, {}) -- No globals. All unknown identifiers evaluate to nil.
272    end
273    local ok, res = pcall(func)
274    if ok then
275      if res == 0 then return false end -- Oh well.
276      return not not res
277    end
278    err = res
279  end
280  wfatal("bad condition: "..err)
281end
282
283-- Skip statements until next conditional pseudo-opcode at the same level.
284local function stmtskip()
285  local dostmt_save = dostmt
286  local lvl = 0
287  dostmt = function(stmt)
288    local op = match(stmt, "^%s*(%S+)")
289    if op == ".if" then
290      lvl = lvl + 1
291    elseif lvl ~= 0 then
292      if op == ".endif" then lvl = lvl - 1 end
293    elseif op == ".elif" or op == ".else" or op == ".endif" then
294      dostmt = dostmt_save
295      dostmt(stmt)
296    end
297  end
298end
299
300-- Pseudo-opcodes for conditional assembly.
301map_coreop[".if_1"] = function(params)
302  if not params then return "condition" end
303  local lvl = condlevel + 1
304  local res = cond_eval(params[1])
305  condlevel = lvl
306  condstack[lvl] = res
307  if not res then stmtskip() end
308end
309
310map_coreop[".elif_1"] = function(params)
311  if not params then return "condition" end
312  if condlevel == 0 then wfatal(".elif without .if") end
313  local lvl = condlevel
314  local res = condstack[lvl]
315  if res then
316    if res == "else" then wfatal(".elif after .else") end
317  else
318    res = cond_eval(params[1])
319    if res then
320      condstack[lvl] = res
321      return
322    end
323  end
324  stmtskip()
325end
326
327map_coreop[".else_0"] = function(params)
328  if condlevel == 0 then wfatal(".else without .if") end
329  local lvl = condlevel
330  local res = condstack[lvl]
331  condstack[lvl] = "else"
332  if res then
333    if res == "else" then wfatal(".else after .else") end
334    stmtskip()
335  end
336end
337
338map_coreop[".endif_0"] = function(params)
339  local lvl = condlevel
340  if lvl == 0 then wfatal(".endif without .if") end
341  condlevel = lvl - 1
342end
343
344-- Check for unfinished conditionals.
345local function checkconds()
346  if g_errcount ~= "fatal" and condlevel ~= 0 then
347    wprinterr(g_fname, ":*: error: unbalanced conditional\n")
348  end
349end
350
351------------------------------------------------------------------------------
352
353-- Search for a file in the given path and open it for reading.
354local function pathopen(path, name)
355  local dirsep = package and match(package.path, "\\") and "\\" or "/"
356  for _,p in ipairs(path) do
357    local fullname = p == "" and name or p..dirsep..name
358    local fin = io.open(fullname, "r")
359    if fin then
360      g_fname = fullname
361      return fin
362    end
363  end
364end
365
366-- Include a file.
367map_coreop[".include_1"] = function(params)
368  if not params then return "filename" end
369  local name = params[1]
370  -- Save state. Ugly, I know. but upvalues are fast.
371  local gf, gl, gcl, gi = g_fname, g_lineno, g_curline, g_indent
372  -- Read the included file.
373  local fatal = readfile(pathopen(g_opt.include, name) or
374			 wfatal("include file `"..name.."' not found"))
375  -- Restore state.
376  g_synclineno = -1
377  g_fname, g_lineno, g_curline, g_indent = gf, gl, gcl, gi
378  if fatal then wfatal("in include file") end
379end
380
381-- Make .include and conditionals initially available, too.
382map_op[".include_1"] = map_coreop[".include_1"]
383map_op[".if_1"] = map_coreop[".if_1"]
384map_op[".elif_1"] = map_coreop[".elif_1"]
385map_op[".else_0"] = map_coreop[".else_0"]
386map_op[".endif_0"] = map_coreop[".endif_0"]
387
388------------------------------------------------------------------------------
389
390-- Support variables for macros.
391local mac_capture, mac_lineno, mac_name
392local mac_active = {}
393local mac_list = {}
394
395-- Pseudo-opcode to define a macro.
396map_coreop[".macro_*"] = function(mparams)
397  if not mparams then return "name [, params...]" end
398  -- Split off and validate macro name.
399  local name = remove(mparams, 1)
400  if not name then werror("missing macro name") end
401  if not (match(name, "^[%a_][%w_%.]*$") or match(name, "^%.[%w_%.]*$")) then
402    wfatal("bad macro name `"..name.."'")
403  end
404  -- Validate macro parameter names.
405  local mdup = {}
406  for _,mp in ipairs(mparams) do
407    if not match(mp, "^[%a_][%w_]*$") then
408      wfatal("bad macro parameter name `"..mp.."'")
409    end
410    if mdup[mp] then wfatal("duplicate macro parameter name `"..mp.."'") end
411    mdup[mp] = true
412  end
413  -- Check for duplicate or recursive macro definitions.
414  local opname = name.."_"..#mparams
415  if map_op[opname] or map_op[name.."_*"] then
416    wfatal("duplicate macro `"..name.."' ("..#mparams.." parameters)")
417  end
418  if mac_capture then wfatal("recursive macro definition") end
419
420  -- Enable statement capture.
421  local lines = {}
422  mac_lineno = g_lineno
423  mac_name = name
424  mac_capture = function(stmt) -- Statement capture function.
425    -- Stop macro definition with .endmacro pseudo-opcode.
426    if not match(stmt, "^%s*.endmacro%s*$") then
427      lines[#lines+1] = stmt
428      return
429    end
430    mac_capture = nil
431    mac_lineno = nil
432    mac_name = nil
433    mac_list[#mac_list+1] = opname
434    -- Add macro-op definition.
435    map_op[opname] = function(params)
436      if not params then return mparams, lines end
437      -- Protect against recursive macro invocation.
438      if mac_active[opname] then wfatal("recursive macro invocation") end
439      mac_active[opname] = true
440      -- Setup substitution map.
441      local subst = {}
442      for i,mp in ipairs(mparams) do subst[mp] = params[i] end
443      local mcom
444      if g_opt.maccomment and g_opt.comment then
445	mcom = " MACRO "..name.." ("..#mparams..")"
446	wcomment("{"..mcom)
447      end
448      -- Loop through all captured statements
449      for _,stmt in ipairs(lines) do
450	-- Substitute macro parameters.
451	local st = gsub(stmt, "[%w_]+", subst)
452	st = definesubst(st)
453	st = gsub(st, "%s*%.%.%s*", "") -- Token paste a..b.
454	if mcom and sub(st, 1, 1) ~= "|" then wcomment(st) end
455	-- Emit statement. Use a protected call for better diagnostics.
456	local ok, err = pcall(dostmt, st)
457	if not ok then
458	  -- Add the captured statement to the error.
459	  wprinterr(err, "\n", g_indent, "|  ", stmt,
460		    "\t[MACRO ", name, " (", #mparams, ")]\n")
461	end
462      end
463      if mcom then wcomment("}"..mcom) end
464      mac_active[opname] = nil
465    end
466  end
467end
468
469-- An .endmacro pseudo-opcode outside of a macro definition is an error.
470map_coreop[".endmacro_0"] = function(params)
471  wfatal(".endmacro without .macro")
472end
473
474-- Dump all macros and their contents (with -PP only).
475local function dumpmacros(out, lvl)
476  sort(mac_list)
477  out:write("Macros:\n")
478  for _,opname in ipairs(mac_list) do
479    local name = sub(opname, 1, -3)
480    local params, lines = map_op[opname]()
481    out:write(format("  %-20s %s\n", name, concat(params, ", ")))
482    if lvl > 1 then
483      for _,line in ipairs(lines) do
484	out:write("  |", line, "\n")
485      end
486      out:write("\n")
487    end
488  end
489  out:write("\n")
490end
491
492-- Check for unfinished macro definitions.
493local function checkmacros()
494  if mac_capture then
495    wprinterr(g_fname, ":", mac_lineno,
496	      ": error: unfinished .macro `", mac_name ,"'\n")
497  end
498end
499
500------------------------------------------------------------------------------
501
502-- Support variables for captures.
503local cap_lineno, cap_name
504local cap_buffers = {}
505local cap_used = {}
506
507-- Start a capture.
508map_coreop[".capture_1"] = function(params)
509  if not params then return "name" end
510  wflush()
511  local name = params[1]
512  if not match(name, "^[%a_][%w_]*$") then
513    wfatal("bad capture name `"..name.."'")
514  end
515  if cap_name then
516    wfatal("already capturing to `"..cap_name.."' since line "..cap_lineno)
517  end
518  cap_name = name
519  cap_lineno = g_lineno
520  -- Create or continue a capture buffer and start the output line capture.
521  local buf = cap_buffers[name]
522  if not buf then buf = {}; cap_buffers[name] = buf end
523  g_capbuffer = buf
524  g_synclineno = 0
525end
526
527-- Stop a capture.
528map_coreop[".endcapture_0"] = function(params)
529  wflush()
530  if not cap_name then wfatal(".endcapture without a valid .capture") end
531  cap_name = nil
532  cap_lineno = nil
533  g_capbuffer = nil
534  g_synclineno = 0
535end
536
537-- Dump a capture buffer.
538map_coreop[".dumpcapture_1"] = function(params)
539  if not params then return "name" end
540  wflush()
541  local name = params[1]
542  if not match(name, "^[%a_][%w_]*$") then
543    wfatal("bad capture name `"..name.."'")
544  end
545  cap_used[name] = true
546  wline(function(out)
547    local buf = cap_buffers[name]
548    if buf then wdumplines(out, buf) end
549  end)
550  g_synclineno = 0
551end
552
553-- Dump all captures and their buffers (with -PP only).
554local function dumpcaptures(out, lvl)
555  out:write("Captures:\n")
556  for name,buf in pairs(cap_buffers) do
557    out:write(format("  %-20s %4s)\n", name, "("..#buf))
558    if lvl > 1 then
559      local bar = rep("=", 76)
560      out:write("  ", bar, "\n")
561      for _,line in ipairs(buf) do
562	out:write("  ", line, "\n")
563      end
564      out:write("  ", bar, "\n\n")
565    end
566  end
567  out:write("\n")
568end
569
570-- Check for unfinished or unused captures.
571local function checkcaptures()
572  if cap_name then
573    wprinterr(g_fname, ":", cap_lineno,
574	      ": error: unfinished .capture `", cap_name,"'\n")
575    return
576  end
577  for name in pairs(cap_buffers) do
578    if not cap_used[name] then
579      wprinterr(g_fname, ":*: error: missing .dumpcapture ", name ,"\n")
580    end
581  end
582end
583
584------------------------------------------------------------------------------
585
586-- Sections names.
587local map_sections = {}
588
589-- Pseudo-opcode to define code sections.
590-- TODO: Data sections, BSS sections. Needs extra C code and API.
591map_coreop[".section_*"] = function(params)
592  if not params then return "name..." end
593  if #map_sections > 0 then werror("duplicate section definition") end
594  wflush()
595  for sn,name in ipairs(params) do
596    local opname = "."..name.."_0"
597    if not match(name, "^[%a][%w_]*$") or
598       map_op[opname] or map_op["."..name.."_*"] then
599      werror("bad section name `"..name.."'")
600    end
601    map_sections[#map_sections+1] = name
602    wline(format("#define DASM_SECTION_%s\t%d", upper(name), sn-1))
603    map_op[opname] = function(params) g_arch.section(sn-1) end
604  end
605  wline(format("#define DASM_MAXSECTION\t\t%d", #map_sections))
606end
607
608-- Dump all sections.
609local function dumpsections(out, lvl)
610  out:write("Sections:\n")
611  for _,name in ipairs(map_sections) do
612    out:write(format("  %s\n", name))
613  end
614  out:write("\n")
615end
616
617------------------------------------------------------------------------------
618
619-- Replacement for customized Lua, which lacks the package library.
620local prefix = ""
621if not require then
622  function require(name)
623    local fp = assert(io.open(prefix..name..".lua"))
624    local s = fp:read("*a")
625    assert(fp:close())
626    return assert(loadstring(s, "@"..name..".lua"))()
627  end
628end
629
630-- Load architecture-specific module.
631local function loadarch(arch)
632  if not match(arch, "^[%w_]+$") then return "bad arch name" end
633  _G._map_def = map_def
634  local ok, m_arch = pcall(require, "dasm_"..arch)
635  if not ok then return "cannot load module: "..m_arch end
636  g_arch = m_arch
637  wflush = m_arch.passcb(wline, werror, wfatal, wwarn)
638  m_arch.setup(arch, g_opt)
639  map_op, map_def = m_arch.mergemaps(map_coreop, map_def)
640end
641
642-- Dump architecture description.
643function opt_map.dumparch(args)
644  local name = optparam(args)
645  if not g_arch then
646    local err = loadarch(name)
647    if err then opterror(err) end
648  end
649
650  local t = {}
651  for name in pairs(map_coreop) do t[#t+1] = name end
652  for name in pairs(map_op) do t[#t+1] = name end
653  sort(t)
654
655  local out = stdout
656  local _arch = g_arch._info
657  out:write(format("%s version %s, released %s, %s\n",
658    _info.name, _info.version, _info.release, _info.url))
659  g_arch.dumparch(out)
660
661  local pseudo = true
662  out:write("Pseudo-Opcodes:\n")
663  for _,sname in ipairs(t) do
664    local name, nparam = match(sname, "^(.+)_([0-9%*])$")
665    if name then
666      if pseudo and sub(name, 1, 1) ~= "." then
667	out:write("\nOpcodes:\n")
668	pseudo = false
669      end
670      local f = map_op[sname]
671      local s
672      if nparam ~= "*" then nparam = nparam + 0 end
673      if nparam == 0 then
674	s = ""
675      elseif type(f) == "string" then
676	s = map_op[".template__"](nil, f, nparam)
677      else
678	s = f(nil, nparam)
679      end
680      if type(s) == "table" then
681	for _,s2 in ipairs(s) do
682	  out:write(format("  %-12s %s\n", name, s2))
683	end
684      else
685	out:write(format("  %-12s %s\n", name, s))
686      end
687    end
688  end
689  out:write("\n")
690  exit(0)
691end
692
693-- Pseudo-opcode to set the architecture.
694-- Only initially available (map_op is replaced when called).
695map_op[".arch_1"] = function(params)
696  if not params then return "name" end
697  local err = loadarch(params[1])
698  if err then wfatal(err) end
699  wline(format("#if DASM_VERSION != %d", _info.vernum))
700  wline('#error "Version mismatch between DynASM and included encoding engine"')
701  wline("#endif")
702end
703
704-- Dummy .arch pseudo-opcode to improve the error report.
705map_coreop[".arch_1"] = function(params)
706  if not params then return "name" end
707  wfatal("duplicate .arch statement")
708end
709
710------------------------------------------------------------------------------
711
712-- Dummy pseudo-opcode. Don't confuse '.nop' with 'nop'.
713map_coreop[".nop_*"] = function(params)
714  if not params then return "[ignored...]" end
715end
716
717-- Pseudo-opcodes to raise errors.
718map_coreop[".error_1"] = function(params)
719  if not params then return "message" end
720  werror(params[1])
721end
722
723map_coreop[".fatal_1"] = function(params)
724  if not params then return "message" end
725  wfatal(params[1])
726end
727
728-- Dump all user defined elements.
729local function dumpdef(out)
730  local lvl = g_opt.dumpdef
731  if lvl == 0 then return end
732  dumpsections(out, lvl)
733  dumpdefines(out, lvl)
734  if g_arch then g_arch.dumpdef(out, lvl) end
735  dumpmacros(out, lvl)
736  dumpcaptures(out, lvl)
737end
738
739------------------------------------------------------------------------------
740
741-- Helper for splitstmt.
742local splitlvl
743
744local function splitstmt_one(c)
745  if c == "(" then
746    splitlvl = ")"..splitlvl
747  elseif c == "[" then
748    splitlvl = "]"..splitlvl
749  elseif c == "{" then
750    splitlvl = "}"..splitlvl
751  elseif c == ")" or c == "]" or c == "}" then
752    if sub(splitlvl, 1, 1) ~= c then werror("unbalanced (), [] or {}") end
753    splitlvl = sub(splitlvl, 2)
754  elseif splitlvl == "" then
755    return " \0 "
756  end
757  return c
758end
759
760-- Split statement into (pseudo-)opcode and params.
761local function splitstmt(stmt)
762  -- Convert label with trailing-colon into .label statement.
763  local label = match(stmt, "^%s*(.+):%s*$")
764  if label then return ".label", {label} end
765
766  -- Split at commas and equal signs, but obey parentheses and brackets.
767  splitlvl = ""
768  stmt = gsub(stmt, "[,%(%)%[%]{}]", splitstmt_one)
769  if splitlvl ~= "" then werror("unbalanced () or []") end
770
771  -- Split off opcode.
772  local op, other = match(stmt, "^%s*([^%s%z]+)%s*(.*)$")
773  if not op then werror("bad statement syntax") end
774
775  -- Split parameters.
776  local params = {}
777  for p in gmatch(other, "%s*(%Z+)%z?") do
778    params[#params+1] = gsub(p, "%s+$", "")
779  end
780  if #params > 16 then werror("too many parameters") end
781
782  params.op = op
783  return op, params
784end
785
786-- Process a single statement.
787dostmt = function(stmt)
788  -- Ignore empty statements.
789  if match(stmt, "^%s*$") then return end
790
791  -- Capture macro defs before substitution.
792  if mac_capture then return mac_capture(stmt) end
793  stmt = definesubst(stmt)
794
795  -- Emit C code without parsing the line.
796  if sub(stmt, 1, 1) == "|" then
797    local tail = sub(stmt, 2)
798    wflush()
799    if sub(tail, 1, 2) == "//" then wcomment(tail) else wline(tail, true) end
800    return
801  end
802
803  -- Split into (pseudo-)opcode and params.
804  local op, params = splitstmt(stmt)
805
806  -- Get opcode handler (matching # of parameters or generic handler).
807  local f = map_op[op.."_"..#params] or map_op[op.."_*"]
808  if not f then
809    if not g_arch then wfatal("first statement must be .arch") end
810    -- Improve error report.
811    for i=0,9 do
812      if map_op[op.."_"..i] then
813	werror("wrong number of parameters for `"..op.."'")
814      end
815    end
816    werror("unknown statement `"..op.."'")
817  end
818
819  -- Call opcode handler or special handler for template strings.
820  if type(f) == "string" then
821    map_op[".template__"](params, f)
822  else
823    f(params)
824  end
825end
826
827-- Process a single line.
828local function doline(line)
829  if g_opt.flushline then wflush() end
830
831  -- Assembler line?
832  local indent, aline = match(line, "^(%s*)%|(.*)$")
833  if not aline then
834    -- No, plain C code line, need to flush first.
835    wflush()
836    wsync()
837    wline(line, false)
838    return
839  end
840
841  g_indent = indent -- Remember current line indentation.
842
843  -- Emit C code (even from macros). Avoids echo and line parsing.
844  if sub(aline, 1, 1) == "|" then
845    if not mac_capture then
846      wsync()
847    elseif g_opt.comment then
848      wsync()
849      wcomment(aline)
850    end
851    dostmt(aline)
852    return
853  end
854
855  -- Echo assembler line as a comment.
856  if g_opt.comment then
857    wsync()
858    wcomment(aline)
859  end
860
861  -- Strip assembler comments.
862  aline = gsub(aline, "//.*$", "")
863
864  -- Split line into statements at semicolons.
865  if match(aline, ";") then
866    for stmt in gmatch(aline, "[^;]+") do dostmt(stmt) end
867  else
868    dostmt(aline)
869  end
870end
871
872------------------------------------------------------------------------------
873
874-- Write DynASM header.
875local function dasmhead(out)
876  out:write(format([[
877/*
878** This file has been pre-processed with DynASM.
879** %s
880** DynASM version %s, DynASM %s version %s
881** DO NOT EDIT! The original file is in "%s".
882*/
883
884]], _info.url,
885    _info.version, g_arch._info.arch, g_arch._info.version,
886    g_fname))
887end
888
889-- Read input file.
890readfile = function(fin)
891  g_indent = ""
892  g_lineno = 0
893  g_synclineno = -1
894
895  -- Process all lines.
896  for line in fin:lines() do
897    g_lineno = g_lineno + 1
898    g_curline = line
899    local ok, err = pcall(doline, line)
900    if not ok and wprinterr(err, "\n") then return true end
901  end
902  wflush()
903
904  -- Close input file.
905  assert(fin == stdin or fin:close())
906end
907
908-- Write output file.
909local function writefile(outfile)
910  local fout
911
912  -- Open output file.
913  if outfile == nil or outfile == "-" then
914    fout = stdout
915  else
916    fout = assert(io.open(outfile, "w"))
917  end
918
919  -- Write all buffered lines
920  wdumplines(fout, g_wbuffer)
921
922  -- Close output file.
923  assert(fout == stdout or fout:close())
924
925  -- Optionally dump definitions.
926  dumpdef(fout == stdout and stderr or stdout)
927end
928
929-- Translate an input file to an output file.
930local function translate(infile, outfile)
931  g_wbuffer = {}
932  g_indent = ""
933  g_lineno = 0
934  g_synclineno = -1
935
936  -- Put header.
937  wline(dasmhead)
938
939  -- Read input file.
940  local fin
941  if infile == "-" then
942    g_fname = "(stdin)"
943    fin = stdin
944  else
945    g_fname = infile
946    fin = assert(io.open(infile, "r"))
947  end
948  readfile(fin)
949
950  -- Check for errors.
951  if not g_arch then
952    wprinterr(g_fname, ":*: error: missing .arch directive\n")
953  end
954  checkconds()
955  checkmacros()
956  checkcaptures()
957
958  if g_errcount ~= 0 then
959    stderr:write(g_fname, ":*: info: ", g_errcount, " error",
960      (type(g_errcount) == "number" and g_errcount > 1) and "s" or "",
961      " in input file -- no output file generated.\n")
962    dumpdef(stderr)
963    exit(1)
964  end
965
966  -- Write output file.
967  writefile(outfile)
968end
969
970------------------------------------------------------------------------------
971
972-- Print help text.
973function opt_map.help()
974  stdout:write("DynASM -- ", _info.description, ".\n")
975  stdout:write("DynASM ", _info.version, " ", _info.release, "  ", _info.url, "\n")
976  stdout:write[[
977
978Usage: dynasm [OPTION]... INFILE.dasc|-
979
980  -h, --help           Display this help text.
981  -V, --version        Display version and copyright information.
982
983  -o, --outfile FILE   Output file name (default is stdout).
984  -I, --include DIR    Add directory to the include search path.
985
986  -c, --ccomment       Use /* */ comments for assembler lines.
987  -C, --cppcomment     Use // comments for assembler lines (default).
988  -N, --nocomment      Suppress assembler lines in output.
989  -M, --maccomment     Show macro expansions as comments (default off).
990
991  -L, --nolineno       Suppress CPP line number information in output.
992  -F, --flushline      Flush action list for every line.
993
994  -D NAME[=SUBST]      Define a substitution.
995  -U NAME              Undefine a substitution.
996
997  -P, --dumpdef        Dump defines, macros, etc. Repeat for more output.
998  -A, --dumparch ARCH  Load architecture ARCH and dump description.
999]]
1000  exit(0)
1001end
1002
1003-- Print version information.
1004function opt_map.version()
1005  stdout:write(format("%s version %s, released %s\n%s\n\n%s",
1006    _info.name, _info.version, _info.release, _info.url, _info.copyright))
1007  exit(0)
1008end
1009
1010-- Misc. options.
1011function opt_map.outfile(args) g_opt.outfile = optparam(args) end
1012function opt_map.include(args) insert(g_opt.include, 1, optparam(args)) end
1013function opt_map.ccomment() g_opt.comment = "/*|"; g_opt.endcomment = " */" end
1014function opt_map.cppcomment() g_opt.comment = "//|"; g_opt.endcomment = "" end
1015function opt_map.nocomment() g_opt.comment = false end
1016function opt_map.maccomment() g_opt.maccomment = true end
1017function opt_map.nolineno() g_opt.cpp = false end
1018function opt_map.flushline() g_opt.flushline = true end
1019function opt_map.dumpdef() g_opt.dumpdef = g_opt.dumpdef + 1 end
1020
1021------------------------------------------------------------------------------
1022
1023-- Short aliases for long options.
1024local opt_alias = {
1025  h = "help", ["?"] = "help", V = "version",
1026  o = "outfile", I = "include",
1027  c = "ccomment", C = "cppcomment", N = "nocomment", M = "maccomment",
1028  L = "nolineno", F = "flushline",
1029  P = "dumpdef", A = "dumparch",
1030}
1031
1032-- Parse single option.
1033local function parseopt(opt, args)
1034  opt_current = #opt == 1 and "-"..opt or "--"..opt
1035  local f = opt_map[opt] or opt_map[opt_alias[opt]]
1036  if not f then
1037    opterror("unrecognized option `", opt_current, "'. Try `--help'.\n")
1038  end
1039  f(args)
1040end
1041
1042-- Parse arguments.
1043local function parseargs(args)
1044  -- Default options.
1045  g_opt.comment = "//|"
1046  g_opt.endcomment = ""
1047  g_opt.cpp = true
1048  g_opt.dumpdef = 0
1049  g_opt.include = { "" }
1050
1051  -- Process all option arguments.
1052  args.argn = 1
1053  repeat
1054    local a = args[args.argn]
1055    if not a then break end
1056    local lopt, opt = match(a, "^%-(%-?)(.+)")
1057    if not opt then break end
1058    args.argn = args.argn + 1
1059    if lopt == "" then
1060      -- Loop through short options.
1061      for o in gmatch(opt, ".") do parseopt(o, args) end
1062    else
1063      -- Long option.
1064      parseopt(opt, args)
1065    end
1066  until false
1067
1068  -- Check for proper number of arguments.
1069  local nargs = #args - args.argn + 1
1070  if nargs ~= 1 then
1071    if nargs == 0 then
1072      if g_opt.dumpdef > 0 then return dumpdef(stdout) end
1073    end
1074    opt_map.help()
1075  end
1076
1077  -- Translate a single input file to a single output file
1078  -- TODO: Handle multiple files?
1079  translate(args[args.argn], g_opt.outfile)
1080end
1081
1082------------------------------------------------------------------------------
1083
1084-- Add the directory dynasm.lua resides in to the Lua module search path.
1085local arg = arg
1086if arg and arg[0] then
1087  prefix = match(arg[0], "^(.*[/\\])")
1088  if package and prefix then package.path = prefix.."?.lua;"..package.path end
1089end
1090
1091-- Start DynASM.
1092parseargs{...}
1093
1094------------------------------------------------------------------------------
1095
1096