Skip to content

Commit

Permalink
DynASM/arm64: allow dynamic register names
Browse files Browse the repository at this point in the history
DynASM/x86 permits register names of the form Ra(expr) where "expr" is
an arbitrary C expression and "Ra" is register class. This allows
registers to be selected dynamically at runtime. However the Arm64 port
only accepts static register names such as "x1", "w5", etc.

This patch extends the DynASM parser for arm64 to accept expressions of
the form Rx(expr) where "x" is a register class such as "x", "w", "q",
etc. A new action DASM_VREG patches the instruction with the dynamic
register index passed as an argument (similar to how dynamic register
names work on x86).

To correctly patch the instruction we need to know the bit position of
the register in the instruction word. This is now passed into
parse_reg() and encoded in the low bits of the DASM_VREG opcode.

To avoid duplication of the bit position in code like
  shl(parse_reg(..., 5), 5)
parse_reg() now returns the shifted register index.

Besides, with the introduction of dynmiac register names, the original
method, i.e 'p[-2]', to accessing 'scale' field for action DASM_IMML,
might be polluted. As a result, load/store with an immediate or type
maps, would be affected.
This patch passes 'scale' as the parameter to DASM_IMML.

Example [1]:

  | cmp Rx(15), php#42
  | mov x3, php#1
  | add Rw(5+1), Rw(7), Rw(22)
  | ldr Rx(4), [Rx(2), php#8]!
  | str x0, [Rx(2), #offsetof(struct stu, age)]
  | ldr x8, STATE->get_ch

Disassembly:

  0xffffb5498000: cmp             x15, #0x2a
  0xffffb5498004: movz            w3, #0x1
  0xffffb5498008: add             w6, w7, w22
  0xffffb549800c: ldr             x4, [x2, php#8]!
  0xffffb5498010: str             x0, [x2, php#8]
  0xffffb5498014: ldr             x8, [x2, php#8]

Test environment:
We're using an ARM-based server, with Ubuntu-20 and GCC-10. Disambler
library capstone[2] should be installed in advance.
After building the PHP, the 'minilua' can be found in
'PHP-SRC/ext/opcache/' directory. Our test case can be run with the
following commands:

  $ PHP-SRC/ext/opcache/minilua \
    PHP-SRC/ext/opcache/jit/dynasm/dynasm.lua -o test.c \
    -D ARM64=1 test-dyn-regs.c
  $ gcc test.c -o test -lcapstone
  $ ./test

[1]
https://github.com/shqking/misc/blob/main/php-dynasm-test/test-dyn-regs.c
[2] https://www.capstone-engine.org/

Co-Developed-by: Nick Gasson <[email protected]>
  • Loading branch information
shqking committed Mar 30, 2021
1 parent 202a701 commit ce4cec9
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 14 deletions.
14 changes: 12 additions & 2 deletions ext/opcache/jit/dynasm/dasm_arm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum {
/* The following actions also have an argument. */
DASM_REL_PC, DASM_LABEL_PC,
DASM_IMM, DASM_IMM6, DASM_IMM12, DASM_IMM13W, DASM_IMM13X, DASM_IMML,
DASM_VREG,
DASM__MAX
};

Expand All @@ -39,6 +40,7 @@ enum {
#define DASM_S_RANGE_LG 0x13000000
#define DASM_S_RANGE_PC 0x14000000
#define DASM_S_RANGE_REL 0x15000000
#define DASM_S_RANGE_VREG 0x16000000
#define DASM_S_UNDEF_LG 0x21000000
#define DASM_S_UNDEF_PC 0x22000000

Expand Down Expand Up @@ -312,13 +314,17 @@ void dasm_put(Dst_DECL, int start, ...)
}
case DASM_IMML: {
#ifdef DASM_CHECKS
int scale = (p[-2] >> 30);
int scale = (ins & 0x3);
CK((!(n & ((1<<scale)-1)) && (unsigned int)(n>>scale) < 4096) ||
(unsigned int)(n+256) < 512, RANGE_I);
#endif
b[pos++] = n;
break;
}
case DASM_VREG:
CK(n < 32, RANGE_VREG);
b[pos++] = n;
break;
}
}
}
Expand Down Expand Up @@ -377,6 +383,7 @@ int dasm_link(Dst_DECL, size_t *szp)
case DASM_IMM: case DASM_IMM6: case DASM_IMM12: case DASM_IMM13W:
case DASM_IMML: pos++; break;
case DASM_IMM13X: pos += 2; break;
case DASM_VREG: pos++; break;
}
}
stop: (void)0;
Expand Down Expand Up @@ -467,11 +474,14 @@ int dasm_encode(Dst_DECL, void *buffer)
cp[-1] |= (dasm_imm13(n, *b++) << 10);
break;
case DASM_IMML: {
int scale = (p[-2] >> 30);
int scale = (ins & 0x3);
cp[-1] |= (!(n & ((1<<scale)-1)) && (unsigned int)(n>>scale) < 4096) ?
((n << (10-scale)) | 0x01000000) : ((n & 511) << 12);
break;
}
case DASM_VREG:
cp[-1] |= (n & 0x1f) << (ins & 0x1f);
break;
default: *cp++ = ins; break;
}
}
Expand Down
36 changes: 24 additions & 12 deletions ext/opcache/jit/dynasm/dasm_arm64.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ local action_names = {
"STOP", "SECTION", "ESC", "REL_EXT",
"ALIGN", "REL_LG", "LABEL_LG",
"REL_PC", "LABEL_PC", "IMM", "IMM6", "IMM12", "IMM13W", "IMM13X", "IMML",
"VREG"
}

-- Maximum number of section buffer positions for dasm_put().
Expand Down Expand Up @@ -246,7 +247,7 @@ local map_cond = {

local parse_reg_type

local function parse_reg(expr)
local function parse_reg(expr, shift)
if not expr then werror("expected register name") end
local tname, ovreg = match(expr, "^([%w_]+):(@?%l%d+)$")
local tp = map_type[tname or expr]
Expand All @@ -266,18 +267,29 @@ local function parse_reg(expr)
elseif parse_reg_type ~= rt then
werror("register size mismatch")
end
return r, tp
return shl(r, shift or 0), tp
end
end
-- Allow Rx(...) for dynamic register names
local vrt, vreg = match(expr, "^R([xwqdshb])(%b())$")
if vreg then
if not parse_reg_type then
parse_reg_type = vrt
elseif parse_reg_type ~= vrt then
werror("register size mismatch")
end
if shift then waction("VREG", shift, vreg) end
return 0
end
werror("bad register name `"..expr.."'")
end

local function parse_reg_base(expr)
if expr == "sp" then return 0x3e0 end
local base, tp = parse_reg(expr)
local base, tp = parse_reg(expr, 5)
if parse_reg_type ~= "x" then werror("bad register type") end
parse_reg_type = false
return shl(base, 5), tp
return base, tp
end

local parse_ctx = {}
Expand Down Expand Up @@ -403,7 +415,7 @@ local function parse_imm_load(imm, scale)
end
werror("out of range immediate `"..imm.."'")
else
waction("IMML", 0, imm)
waction("IMML", scale, imm)
return 0
end
end
Expand Down Expand Up @@ -470,7 +482,7 @@ local function parse_load(params, nparams, n, op)
if reg and tailr ~= "" then
local base, tp = parse_reg_base(reg)
if tp then
waction("IMML", 0, format(tp.ctypefmt, tailr))
waction("IMML", shr(op, 30), format(tp.ctypefmt, tailr))
return op + base
end
end
Expand All @@ -494,7 +506,7 @@ local function parse_load(params, nparams, n, op)
op = op + parse_imm_load(imm, scale)
else
local p2b, p3b, p3s = match(p2a, "^,%s*([^,%s]*)%s*,?%s*(%S*)%s*(.*)$")
op = op + shl(parse_reg(p2b), 16) + 0x00200800
op = op + parse_reg(p2b, 16) + 0x00200800
if parse_reg_type ~= "x" and parse_reg_type ~= "w" then
werror("bad index register type")
end
Expand Down Expand Up @@ -891,15 +903,15 @@ local function parse_template(params, template, nparams, pos)
for p in gmatch(sub(template, 9), ".") do
local q = params[n]
if p == "D" then
op = op + parse_reg(q); n = n + 1
op = op + parse_reg(q, 0); n = n + 1
elseif p == "N" then
op = op + shl(parse_reg(q), 5); n = n + 1
op = op + parse_reg(q, 5); n = n + 1
elseif p == "M" then
op = op + shl(parse_reg(q), 16); n = n + 1
op = op + parse_reg(q, 16); n = n + 1
elseif p == "A" then
op = op + shl(parse_reg(q), 10); n = n + 1
op = op + parse_reg(q, 10); n = n + 1
elseif p == "m" then
op = op + shl(parse_reg(params[n-1]), 16)
op = op + parse_reg(params[n-1], 16)

elseif p == "p" then
if q == "sp" then params[n] = "@x31" end
Expand Down

0 comments on commit ce4cec9

Please sign in to comment.