Skip to content

Commit f4b0720

Browse files
Merge pull request #50 from wnienhaus/fix-relative-jumps
Fix relative jumps
2 parents 2844227 + c29b7a2 commit f4b0720

File tree

4 files changed

+172
-46
lines changed

4 files changed

+172
-46
lines changed

Diff for: .github/workflows/run_tests.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- name: Install dependencies
1414
run: |
1515
sudo apt-get update
16-
sudo apt-get install -y git build-essential libffi-dev pkg-config python3 bison flex
16+
sudo apt-get install -y git build-essential libffi-dev pkg-config python3 bison flex xxd
1717
1818
- name: Record version
1919
run: |

Diff for: esp32_ulp/assemble.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -263,16 +263,22 @@ def assembler_pass(self, lines):
263263
continue
264264
else:
265265
# machine instruction
266-
func = getattr(opcodes, 'i_' + opcode.lower(), None)
266+
opcode_lower = opcode.lower()
267+
func = getattr(opcodes, 'i_' + opcode_lower, None)
267268
if func is not None:
268-
# during the first pass, symbols are not all known yet.
269-
# so some expressions may not evaluate to something (yet).
270-
# instruction building requires sane arguments however.
271-
# since all instructions are 4 bytes long, we simply skip
272-
# building instructions during pass 1, and append an "empty
273-
# instruction" to the section to get the right section size.
274-
instruction = 0 if self.a_pass == 1 else func(*args)
275-
self.append_section(instruction.to_bytes(4, 'little'), TEXT)
269+
if self.a_pass == 1:
270+
# during the first pass, symbols are not all known yet.
271+
# so we add empty instructions to the section, to determine
272+
# section sizes and symbol offsets for pass 2.
273+
result = (0,) * opcodes.no_of_instr(opcode_lower, args)
274+
else:
275+
result = func(*args)
276+
277+
if not isinstance(result, tuple):
278+
result = (result,)
279+
280+
for instruction in result:
281+
self.append_section(instruction.to_bytes(4, 'little'), TEXT)
276282
continue
277283
raise ValueError('Unknown opcode or directive: %s' % opcode)
278284
self.finalize_sections()

Diff for: esp32_ulp/opcodes.py

+92-36
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,19 @@
4747
ALU_SEL_RST = 2
4848

4949
OPCODE_BRANCH = 8
50+
# https://github.com/espressif/binutils-esp32ulp/blob/d61f86f97eda43fc118df30d019fc062aaa6bc8d/include/opcode/esp32ulp_esp32.h#L85
5051
SUB_OPCODE_BX = 0
52+
SUB_OPCODE_BR = 1
53+
SUB_OPCODE_BS = 2
5154
BX_JUMP_TYPE_DIRECT = 0
5255
BX_JUMP_TYPE_ZERO = 1
5356
BX_JUMP_TYPE_OVF = 2
54-
SUB_OPCODE_B = 1
55-
B_CMP_L = 0
56-
B_CMP_GE = 1
57-
SUB_OPCODE_BC = 2
58-
BC_CMP_LT = 0
59-
BC_CMP_GT = 1
60-
BC_CMP_EQ = 2
57+
# https://github.com/espressif/binutils-esp32ulp/blob/d61f86f97eda43fc118df30d019fc062aaa6bc8d/gas/config/tc-esp32ulp.h#L91
58+
BRCOND_LT = 0
59+
BRCOND_GE = 1
60+
BRCOND_LE = 2
61+
BRCOND_EQ = 3
62+
BRCOND_GT = 4
6163

6264
OPCODE_END = 9
6365
SUB_OPCODE_END = 0
@@ -210,23 +212,23 @@ def make_ins(layout):
210212
""")
211213

212214

213-
_b = make_ins("""
215+
_br = make_ins("""
214216
imm : 16 # Immediate value to compare against
215-
cmp : 1 # Comparison to perform: B_CMP_L or B_CMP_GE
217+
cmp : 1 # Comparison to perform: BRCOND_LT or BRCOND_GE
216218
offset : 7 # Absolute value of target PC offset w.r.t. current PC, expressed in words
217219
sign : 1 # Sign of target PC offset: 0: positive, 1: negative
218-
sub_opcode : 3 # Sub opcode (SUB_OPCODE_B)
220+
sub_opcode : 3 # Sub opcode (SUB_OPCODE_BR)
219221
opcode : 4 # Opcode (OPCODE_BRANCH)
220222
""")
221223

222224

223-
_bc = make_ins("""
225+
_bs = make_ins("""
224226
imm : 8 # Immediate value to compare against
225227
unused : 7 # Unused
226-
cmp : 2 # Comparison to perform: BC_CMP_LT, GT or EQ
228+
cmp : 2 # Comparison to perform: BRCOND_LT, GT or EQ
227229
offset : 7 # Absolute value of target PC offset w.r.t. current PC, expressed in words
228230
sign : 1 # Sign of target PC offset: 0: positive, 1: negative
229-
sub_opcode : 3 # Sub opcode (SUB_OPCODE_BC)
231+
sub_opcode : 3 # Sub opcode (SUB_OPCODE_BS)
230232
opcode : 4 # Opcode (OPCODE_BRANCH)
231233
""")
232234

@@ -299,7 +301,7 @@ def arg_qualify(arg):
299301
if 0 <= reg <= 3:
300302
return ARG(REG, reg, arg)
301303
raise ValueError('arg_qualify: valid registers are r0, r1, r2, r3. Given: %s' % arg)
302-
if arg_lower in ['--', 'eq', 'ov', 'lt', 'gt', 'ge']:
304+
if arg_lower in ['--', 'eq', 'ov', 'lt', 'gt', 'ge', 'le']:
303305
return ARG(COND, arg_lower, arg)
304306
try:
305307
return ARG(IMM, int(arg), arg)
@@ -338,7 +340,9 @@ def get_rel(arg):
338340
if isinstance(arg, str):
339341
arg = arg_qualify(arg)
340342
if arg.type == IMM:
341-
return arg.value
343+
if arg.value & 3 != 0: # bitwise version of: arg.value % 4 != 0
344+
raise ValueError('Relative offset must be a multiple of 4')
345+
return arg.value >> 2 # bitwise version of: arg.value // 4
342346
if arg.type == SYM:
343347
return symbols.resolve_relative(arg.value)
344348
raise TypeError('wanted: immediate, got: %s' % arg.raw)
@@ -634,41 +638,93 @@ def i_jump(target, condition='--'):
634638
raise TypeError('unsupported operand: %s' % target.raw)
635639

636640

641+
def _jump_relr(threshold, cond, offset):
642+
"""
643+
Equivalent of I_JUMP_RELR macro in binutils-esp32ulp
644+
"""
645+
_br.imm = threshold
646+
_br.cmp = cond
647+
_br.offset = abs(offset)
648+
_br.sign = 0 if offset >= 0 else 1
649+
_br.sub_opcode = SUB_OPCODE_BR
650+
_br.opcode = OPCODE_BRANCH
651+
return _br.all
652+
653+
637654
def i_jumpr(offset, threshold, condition):
638655
offset = get_rel(offset)
639656
threshold = get_imm(threshold)
640657
condition = get_cond(condition)
641658
if condition == 'lt':
642-
cmp_op = B_CMP_L
659+
cmp_op = BRCOND_LT
643660
elif condition == 'ge':
644-
cmp_op = B_CMP_GE
661+
cmp_op = BRCOND_GE
662+
elif condition == 'le': # le == lt(threshold+1)
663+
threshold += 1
664+
cmp_op = BRCOND_LT
665+
elif condition == 'gt': # gt == ge(threshold+1)
666+
threshold += 1
667+
cmp_op = BRCOND_GE
668+
elif condition == 'eq': # eq == ge(threshold) but not ge(threshold+1)
669+
# jump over next JUMPR
670+
skip_ins = _jump_relr(threshold + 1, BRCOND_GE, 2)
671+
# jump to target
672+
offset -= 1 # adjust for the additional JUMPR instruction
673+
jump_ins = _jump_relr(threshold, BRCOND_GE, offset)
674+
return (skip_ins, jump_ins)
645675
else:
646676
raise ValueError("invalid comparison condition")
647-
_b.imm = threshold
648-
_b.cmp = cmp_op
649-
_b.offset = abs(offset)
650-
_b.sign = 0 if offset >= 0 else 1
651-
_b.sub_opcode = SUB_OPCODE_B
652-
_b.opcode = OPCODE_BRANCH
653-
return _b.all
677+
return _jump_relr(threshold, cmp_op, offset)
678+
679+
680+
def _jump_rels(threshold, cond, offset):
681+
"""
682+
Equivalent of I_JUMP_RELS macro in binutils-esp32ulp
683+
"""
684+
_bs.imm = threshold
685+
_bs.cmp = cond
686+
_bs.offset = abs(offset)
687+
_bs.sign = 0 if offset >= 0 else 1
688+
_bs.sub_opcode = SUB_OPCODE_BS
689+
_bs.opcode = OPCODE_BRANCH
690+
return _bs.all
654691

655692

656693
def i_jumps(offset, threshold, condition):
657694
offset = get_rel(offset)
658695
threshold = get_imm(threshold)
659696
condition = get_cond(condition)
660697
if condition == 'lt':
661-
cmp_op = BC_CMP_LT
662-
elif condition == 'gt':
663-
cmp_op = BC_CMP_GT
664-
elif condition == 'eq':
665-
cmp_op = BC_CMP_EQ
698+
cmp_op = BRCOND_LT
699+
elif condition == 'le':
700+
cmp_op = BRCOND_LE
701+
elif condition == 'ge':
702+
cmp_op = BRCOND_GE
703+
elif condition in ('eq', 'gt'):
704+
if condition == 'eq': # eq == le but not lt
705+
skip_cond = BRCOND_LT
706+
jump_cond = BRCOND_LE
707+
elif condition == 'gt': # gt == ge but not le
708+
skip_cond = BRCOND_LE
709+
jump_cond = BRCOND_GE
710+
711+
# jump over next JUMPS
712+
skip_ins = _jump_rels(threshold, skip_cond, 2)
713+
# jump to target
714+
offset -= 1 # adjust for the additional JUMPS instruction
715+
jump_ins = _jump_rels(threshold, jump_cond, offset)
716+
717+
return (skip_ins, jump_ins)
666718
else:
667719
raise ValueError("invalid comparison condition")
668-
_bc.imm = threshold
669-
_bc.cmp = cmp_op
670-
_bc.offset = abs(offset)
671-
_bc.sign = 0 if offset >= 0 else 1
672-
_bc.sub_opcode = SUB_OPCODE_BC
673-
_bc.opcode = OPCODE_BRANCH
674-
return _bc.all
720+
return _jump_rels(threshold, cmp_op, offset)
721+
722+
723+
def no_of_instr(opcode, args):
724+
if opcode == 'jumpr' and get_cond(args[2]) == 'eq':
725+
return 2
726+
727+
if opcode == 'jumps' and get_cond(args[2]) in ('eq', 'gt'):
728+
return 2
729+
730+
return 1

Diff for: tests/compat/jumps.S

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
.text
2+
.set const, 3
3+
.global const # exporting symbol is required for binutils, not important for py-esp32-ulp
4+
5+
entry:
6+
nop
7+
8+
# jumps with labels
9+
jumps entry, 42, lt
10+
jumps entry, 42, lt
11+
jumps later, 42, lt
12+
jumps entry, 42, le
13+
jumps later, 42, le
14+
jumps entry, 42, ge
15+
jumps later, 42, ge
16+
jumps entry, 42, eq
17+
jumps later, 42, eq
18+
jumps entry, 42, gt
19+
jumps later, 42, gt
20+
21+
# jumps with immediate offset (specified in bytes, but real instruction uses words)
22+
jumps 0, 42, lt
23+
24+
jumps 4, 42, lt
25+
jumps 8, 42, lt
26+
jumps 32, 42, lt
27+
28+
jumps -4, 42, lt
29+
jumps -8, 42, lt
30+
jumps -32, 42, lt
31+
32+
# jumps with immediate offset from absolute symbol
33+
jumps const, 42, lt
34+
35+
# jumpr with labels
36+
jumpr entry, 42, lt
37+
jumpr later, 42, lt
38+
jumpr entry, 42, ge
39+
jumpr later, 42, ge
40+
jumpr entry, 42, le
41+
jumpr later, 42, le
42+
jumpr entry, 42, gt
43+
jumpr later, 42, gt
44+
jumpr entry, 42, eq
45+
jumpr later, 42, eq
46+
47+
# jumpr with immediate offset (specified in bytes, but real instruction uses words)
48+
jumpr 0, 42, lt
49+
50+
jumpr 4, 42, lt
51+
jumpr 8, 42, lt
52+
jumpr 32, 42, lt
53+
54+
jumpr -4, 42, lt
55+
jumpr -8, 42, lt
56+
jumpr -32, 42, lt
57+
58+
# jumps with immediate offset from absolute symbol
59+
jumpr const, 42, lt
60+
61+
nop
62+
nop
63+
nop
64+
later:

0 commit comments

Comments
 (0)