init (v0.1.0a4)

This commit is contained in:
sup39 2023-07-14 20:48:11 +09:00
commit 48e39bc2bd
No known key found for this signature in database
GPG key ID: 14D2E0D21140D260
11 changed files with 495 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/dist
*.egg-info/
__pycache__/

9
CHANGELOG.md Normal file
View file

@ -0,0 +1,9 @@
# supGecko
## 0.1.0a4
- Added `compile_flags` property to `Gecko` class
## 0.1.0a3
- Added C/ASM compilation support for C0, C2, c\_kit
## 0.1.0a2
- Added `indent` parameter for `Gecko#dump_txt()`
## 0.1.0a1
- Initialized

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 sup39
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
README.md Normal file
View file

@ -0,0 +1,16 @@
# supGecko
A helper library to write Gecko code
## Installation
```
pip install supGecko
```
## Usage
```python
from supGecko import Gecko
g = Gecko()
# TODO: usage of the Gecko class
print(g.dump_txt())
```

3
pyproject.toml Normal file
View file

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"

23
setup.cfg Normal file
View file

@ -0,0 +1,23 @@
[metadata]
name = supGecko
version = 0.1.0a4
author = sup39
author_email = sms@sup39.dev
description = A helper library to write Gecko code
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/sup39/supGecko
license = MIT
project_urls =
Bug Tracker = https://github.com/sup39/supGecko/issues
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
[options]
include_package_data = True
packages = find:
python_requires = >=3.8
[options.packages.find]
where = src

4
src/supGecko/__init__.py Normal file
View file

@ -0,0 +1,4 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2023 sup39
from .gecko import Gecko

108
src/supGecko/asm.py Normal file
View file

@ -0,0 +1,108 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2023 sup39
import shutil
from distutils import spawn
import tempfile
import os
import subprocess
def system(argv, *args, **kwargs):
r = subprocess.run(argv, *args, capture_output=True, text=True, **kwargs)
if r.returncode:
raise Exception(f'Fail to run {argv[0]} (code={r.returncode}): {r.stderr}')
return r.stdout
def write_extra_input(x, file):
if type(x) == str:
print(x, file=file)
else:
for line in x:
print(line, file=file)
def compile(
input_path, addr=None,
extra_c_flags=[],
extra_as_input=[], extra_as_flags=[],
extra_ld_input=[], extra_ld_flags=[],
):
distDir = tempfile.mkdtemp()
distASM, distOBJ, distLD, distLOBJ, distBIN = (f'{distDir}/_.{ext}' for ext in ['s', 'o', 'ld', 'l.o', 'bin'])
try:
input_name = input_path.rsplit('.', 1)[0]
if input_path.endswith('.c'):
# compile to OBJ
system([
'powerpc-eabi-gcc',
'-fno-asynchronous-unwind-tables',
','.join((
'-Wa', '-mregnames', '-mgekko',
*extra_as_flags,
)),
'-c', '-o', distOBJ,
*extra_c_flags,
input_path,
])
else: # treat as ASM file
# make ASM file
with open(distASM, 'w') as fw, open(input_path, 'r') as fr:
write_extra_input(extra_as_input, file=fw)
for line in fr: fw.write(line)
# assemble to OBJ
system([
'powerpc-eabi-as',
'-mregnames', '-mgekko',
'-o', distOBJ,
*extra_as_flags,
distASM,
])
# link
with open(distLD, 'w') as fw:
inputLD = input_name+'.ld'
## extra ld
if os.path.isfile(inputLD):
with open(inputLD) as f:
fw.write(f.read())
## section
print('SECTIONS {', file=fw)
if addr is not None: print(f' . = {addr};', file=fw)
print(' .text : ALIGN(4) { *(.text) }', file=fw)
if addr is not None: print('.rodata : ALIGN(4) { *(.rodata) }', file=fw)
print('}', file=fw)
write_extra_input(extra_ld_input, file=fw)
system([
'powerpc-eabi-ld',
'-o', distLOBJ,
'-T', distLD,
*extra_ld_flags,
distOBJ,
])
# gecko symbols
symbols = {}
lines = system([
'powerpc-eabi-objdump',
'-tj.text', distLOBJ,
]).split('\n')
for line in lines[4:-3]:
ch1, ch2 = line.split('\t')
addr = int(ch1.split(None, 2)[0], 16)
name = ch2.split()[1]
symbols[name] = addr
# binary
system([
'powerpc-eabi-objcopy',
'-O', 'binary',
distLOBJ, distBIN,
])
with open(distBIN, 'rb') as f:
codeBin = f.read()
return codeBin, symbols
finally:
shutil.rmtree(distDir)

22
src/supGecko/consts.py Normal file
View file

@ -0,0 +1,22 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2023 sup39
CMP_IDX = {'==': 0, '!=': 2, '>': 4, '<': 6}
UNIT_IDX = {
8: 0, 'b': 0, 'byte': 0,
16: 1, 'h': 1, 'halfword': 1,
32: 2, 'w': 2, 'word': 2,
}
REGOP_IDX = {
'add': 0, '+': 0,
'mul': 1, '*': 1,
'or' : 2, '|': 2,
'and': 3, '&': 3,
'xor': 4, '^': 4,
'slw': 5, '<<': 5,
'srw': 6, '>>': 6,
'rol': 7,
'asr': 8,
'fadds': 9,
'fmuls': 10,
}

248
src/supGecko/gecko.py Normal file
View file

@ -0,0 +1,248 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2023 sup39
from .asm import compile
from .utils import *
from .consts import *
# TODO: assert range of arg
class Gecko():
def __init__(self, compile_flags={}):
self.code = bytearray()
self.compile_flags = compile_flags
def append(self, *payloads):
self.code += b''.join(map(parse_binarg, payloads))
return self
def dump_txt(self, indent=''):
if type(indent) == int: indent = ' '*indent
return '\n'.join(
indent+' '.join(self.code[j:j+4].hex().upper() for j in [i, i+4])
for i in range(0, len(self.code), 8)
)
def compile(self, input_path, addr=None, **kwargs0):
kwargs = {'extra_'+k: v for k, v in self.compile_flags.items()}
for k, v in kwargs0.items():
if type(v) == str: v = [v]
kwargs[k] = kwargs[k] + v if k in kwargs else v
return compile(input_path, addr, **kwargs)
''' 00 '''
def write8(self, addr, val, count=1, po=False):
return self.append(
cw_addr(0x00, addr, po),
((count-1)&0xffff) << 16 | val&0xff,
)
''' 02 '''
def write16(self, addr, val, count=1, po=False):
return self.append(
cw_addr(0x02, addr, po),
((count-1)&0xffff) << 16 | val&0xffff,
)
''' 04 '''
def write32(self, addr, val, po=False):
return self.append(
cw_addr(0x04, addr, po),
val & 0xffffffff,
)
''' 06 '''
def write_string(self, addr, payload, po=False):
if type(payload) == str: payload = bytes.fromhex(payload)
size = len(payload)
rsize = size%8
if rsize: payload += b'\x00'*(8-rsize)
return self.append(cw_addr(0x06, addr, po), size, payload)
''' 08 '''
def write_addr(self, addr, n, unit, val, addr_step, val_step, po=False):
t = UNIT_IDX[unit]
return self.append(
cw_addr(0x08, addr, po),
val,
t<<28 | (n&0xfff)<<16 | (addr_step&0xffff),
val_step & 0xffffffff,
)
''' 20-27 '''
def if32(self, addr, op, val, po=False, endif=False):
ct = 0x20 | CMP_IDX[op]
return self.append(
cw_addr(ct, addr, po, endif),
val & 0xffffffff,
)
''' 28-2F '''
def if16(self, addr, op, val, mask=-1, po=False, endif=False):
ct = 0x28 | CMP_IDX[op]
return self.append(
cw_addr(ct, addr, po, endif),
(~mask & 0xffff)<<16 | val & 0xffff,
)
''' 4_TYZ '''
def sl_bapo(self, action, target, op, addr, bapo=None, gr=None):
cw = ({'ba': 0x40000, 'po': 0x48000}[target] \
| {'load': 0x00000, 'set': 0x02000, 'store': 0x04000} \
| {'+=': 0x00100, '=': 0}[op] \
| {'ba': 0x00010, 'po': 0x10010, None: 0}[bapo]
) << 12
if action == 'store':
assert op == '=', '`op` should be "=" when storing ba/po'
if gr is not None:
cw |= 0x1_000 | gr
return self.append(cw, addr&0xffffffff)
def load_ba(self, *args, **kwargs):
return self.sl_bapo('load', 'ba', *args, **kwargs)
def set_ba(self, *args, **kwargs):
return self.sl_bapo('set', 'ba', *args, **kwargs)
def store_ba(self, *args, **kwargs):
return self.sl_bapo('store', 'ba', *args, **kwargs)
def load_po(self, *args, **kwargs):
return self.sl_bapo('load', 'po', *args, **kwargs)
def set_po(self, *args, **kwargs):
return self.sl_bapo('set', 'po', *args, **kwargs)
def store_po(self, *args, **kwargs):
return self.sl_bapo('store', 'po', *args, **kwargs)
''' 46, 4E '''
def store_ncl(self, target, offset):
ct = {'ba': 0x46, 'po': 0x4E}[target]
return self.append(ct<<24 | offset&0xffff, 0)
''' 60 '''
def set_repeat(self, n, p):
return self.append(0x60<<24 | n&0xffff, p&0xf)
''' 62 '''
def execute_repeat(self, p):
return self.append(0x62<<24, p&0xf)
''' 64 '''
def return_(self, p, if_=None):
return self.append(cw_go(0x64, if_, n=0), p&0xf)
''' 66 '''
def goto(self, n, p, if_=None):
return self.append(cw_go(0x66, if_, n), p&0xf)
''' 68 '''
def gosub(self, n, p, if_=None):
return self.append(cw_go(0x68, if_, n), p&0xf)
''' 80 '''
def set_reg(self, gr, op, addr, bapo=None):
c = {'ba': 0x8001, 'po': 0x9001, None: 0x8000}[bapo] \
| {'+=': 0x0010, '=': 0}[op]
return self.append(c<<16 | gr&0xf, addr&0xffffffff)
def load_reg(self, gr, addr, unit=None):
c = {'ba': 0x8201, 'po': 0x9201, None: 0x8200}[bapo]
u = UNIT_IDX[unit]
return self.append(c<<16 | u<<20 | gr&0xf, addr&0xffffffff)
def store_reg(self, gr, addr, unit=None, count=1):
c = {'ba': 0x8401, 'po': 0x9401, None: 0x8400}[bapo]
t = UNIT_IDX[unit]
y = (count - 1) & 0xfff
return self.append(
c<<16 | t<<20 | y<<4 | gr&0xf,
addr & 0xffffffff,
)
''' 86 '''
def reg_op_imm(self, lhs, op, rhs):
cw, rhs = parse_regop(0x86, lhs, op, rhs)
return self.append(cw, rhs & 0xffffffff)
''' 88 '''
def reg_op_reg(self, lhs, op, rhs):
cw, rhs = parse_regop(0x88, lhs, op, rhs)
return self.append(cw, rhs & 0xf)
'''
[8A] (K, XXXXXXXX) <- N
[8C] K <- (N, XXXXXXXX)
'''
def memcpy(self, dst, src, n):
if type(src) == int:
assert type(dst)==tuple and len(dst)==2, \
'`dst` should be in form of "(K, XXXXXXXX)"'
N = src&0xf
K, off = dst
ct, K = parse_regidx(0x8A, K)
else:
assert type(dst) == int, \
'`dst` and `src` cannot be tuple at the same time'
assert len(src)==2, '`src` should be in form of "(N, XXXXXXXX)"'
ct = 0x8C
N, off = src
K = dst&0xf
ct, N = parse_regidx(0x8C, N)
return self.append(
ct<<24 | (n & 0xffff)<<8 | N<<4 | K,
off & 0xffffffff,
)
''' A0-A7 '''
def if16_reg(self, N, op, K, offset=None, mask=-1, endif=False):
ct = 0xA0 | CMP_IDX[op]
ctK, K = parse_regidx(ct, K)
ctN, N = parse_regidx(ct, N)
if K==0xf or N==0xf:
assert offset is not None, '`offset` should be set if ba/po is used'
else:
offset = 0
assert ctK == ctN, 'ba and po cannot be used at the same time'
ct = ctN
return self.append(
cw_addr(ct, addr, po=False, endif=endif)
(K<<28 | N<<24 | ~mask&0xffff),
)
''' A8-AF '''
def if16_cnt(self, cnt, op, val, reset_on_true, mask=-1, endif=False):
T = (8 if reset_on_true else 0) + (1 if endif else 0)
return self.append(
(0xA8 | CMP_IDX[op])<<24 | (cnt&0xffff)<<4 | T,
~mask<<16 | val&0xffff,
)
''' C0 '''
def C0(self, input_path=None, raw=None, **kwargs):
code = make_asm_code(self.compile, input_path, raw, kwargs)
if len(code)%8 == 4: # pad code with blr
code += b'\x4E\x80\x00\x20'
return self.append(0xC000_0000, len(code)>>3, code)
''' C2 '''
def C2(self, addr, input_path, raw=None, po=False, **kwargs):
code = make_asm_code(self.compile, input_path, raw, kwargs)
if len(code)%8 == 0: # pad code with nop
code += b'\x60\x00\x00\x00'
code += b'\x00\x00\x00\x00' # append 0
return self.append(cw_addr(0xC2, addr, po), len(code)>>3, code)
''' C6 '''
def branch(self, addr, dst, po=False):
return self.append(cw_addr(0xC6, addr, po), dst&0xffffffff)
''' CC '''
def onoff_switch(self):
return self.append(0xCC00_0000, 0)
''' CE '''
def addr_range_check(self, l, u, endif=False):
return self.append(
cw_addr(0xCE, 0, po, endif),
(l&0xffff)<<16 | (u&0xffff),
)
''' E0 '''
def full_terminator(self, ba=0, po=0):
return self.append(0xE000_0000, (ba&0xffff)<<16 | (po&0xffff))
''' E2 '''
def endif(self, count=None, else_=False, ba=0, po=0):
if count is None:
if else_: count = 1
else: return self.full_terminator(ba, po)
return self.append(
0xE200_0000 | (0x1_000_00 if else_ else 0) | count&0xff,
(ba&0xffff)<<16 | (po&0xffff),
)
''' F0 '''
def end_of_code(self):
return self.append(0xF000_0000, 0)
''' C Kit '''
def c_kit(self, addr_code, input_path, entries, **kwargs):
code, symbols = self.compile(input_path, addr_code, **kwargs)
# 06
self.write_string(addr_code, code, po=False)
# 04/C6
for addr_src, action, name in entries:
assert name in symbols, f'symbol "{name}" is not found in the .text section of {input_path}'
addr_dst = symbols[name]
if action == 'b':
self.branch(addr_src, addr_dst, po=False)
elif action == 'bl':
d_addr = addr_dst - addr_src
assert -0x200_0000 <= d_addr < 0x200_0000, 'cannot bl from %08X to %08X'%(addr_src, addr_dst)
inst = 0x4800_0001 | d_addr&0x3ff_fffc
self.write32(addr_src, inst, po=False)
else: assert False, '`action` should be "b" or "bl", got "{action}"'
return self

38
src/supGecko/utils.py Normal file
View file

@ -0,0 +1,38 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2023 sup39
def cw_addr(ct, addr, po, endif=False):
return (ct+0x10 if po else ct)<<24 | (addr+1 if endif else addr)&0x1ff_ffff
def cw_go(ct, if_, n):
return ct<<24 | [True, False, None].index(if_)<<20 | n&0xffff
def parse_regop(ct, lhs, op, rhs):
if op.endswith('='): op = op[:-1] # drop trailing =
lhs, lhs_flag = parse_bracket_operand(lhs, 'lhs')
rhs, rhs_flag = parse_bracket_operand(rhs, 'rhs')
cw = ct<<24 | REGOP_IDX[op]<<20 | lhs_flag<<16 | rhs_flag<<17 | lhs&0xf
return cw, rhs
def parse_regidx(ct, x, name):
if x == 'ba': x = 0xf
elif x == 'po': x = 0xf; ct |= 0x10
else: x &= 0xf; assert x!=0xf, f'{name} cannot be F'
return ct, x
def parse_bracket_operand(x, name): # returns (value, hasBracket)
if type(x) == list:
assert len(x)==1, f'`{name}` must be "x" or "[x]"'
return x, 1
return x, 0
def parse_binarg(x):
return b''.join(map(parse_binarg, x)) if type(x)==list else \
bytes.fromhex(x) if type(x)==str else \
x.to_bytes(4, 'big') if type(x)==int else x
def make_asm_code(compile, input_path, raw, kwargs):
if raw is not None: # raw code
code = parse_binarg(raw)
assert len(code)%4 == 0, \
f'len(raw) should a multiple of 4, got {len(raw)}'
else: # compile from file
assert input_path is not None, 'either `input_path` or `raw` should be set'
code, symbols = compile(input_path, **kwargs)
return code