diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 956312b..303304f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.6.0 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.1 + rev: 3.9.2 hooks: - id: flake8 diff --git a/lcc/src/bytecode.c b/lcc/src/bytecode.c index 871056a..0319c13 100644 --- a/lcc/src/bytecode.c +++ b/lcc/src/bytecode.c @@ -1,6 +1,7 @@ #include "c.h" #define I(f) b_##f +static int dump_stack = 0; static void I(segment)(int n) { static int cseg; @@ -219,9 +220,15 @@ static void I(local)(Symbol p) { p->x.name = stringf("%d", offset); p->x.offset = offset; offset += p->type->size; + if (dump_stack) print("local %s %s %f\n", p->x.name, p->name, p->ref); } -static void I(progbeg)(int argc, char *argv[]) {} +static void I(progbeg)(int argc, char *argv[]) { + int i; + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-dump-stack")) dump_stack = 1; + } +} static void I(progend)(void) {} diff --git a/quatch/_compile.py b/quatch/_compile.py index 53e7559..4ee5240 100644 --- a/quatch/_compile.py +++ b/quatch/_compile.py @@ -22,7 +22,7 @@ import os import shutil import subprocess -from typing import Iterable, Optional +from typing import Iterable, List, Optional class CompilerError(Exception): @@ -30,7 +30,10 @@ class CompilerError(Exception): def compile_c_file( - input_path: str, output_path: str, include_dirs: Optional[Iterable[str]] = None + input_path: str, + output_path: str, + include_dirs: Optional[Iterable[str]] = None, + additional_args: Optional[List[str]] = None, ) -> str: """Compile C code into lcc bytecode. @@ -54,6 +57,8 @@ def compile_c_file( "-Wf-target=bytecode", "-Wf-g", ] + if additional_args is not None: + command += additional_args if include_dirs is not None: command += [f"-I{include_dir}" for include_dir in include_dirs] command += ["-o", output_path, input_path] diff --git a/quatch/_instruction.py b/quatch/_instruction.py index f9f6090..ce8ea66 100644 --- a/quatch/_instruction.py +++ b/quatch/_instruction.py @@ -134,10 +134,16 @@ class Instruction: Instruction(Opcode.CONST, 0x7b) """ - def __init__(self, opcode: Opcode, operand: Optional[Operand] = None) -> None: + def __init__( + self, + opcode: Opcode, + operand: Optional[Operand] = None, + debug_info: Optional[str] = "", + ) -> None: """Initialize an Instruction from an opcode and operand.""" self._opcode: Opcode = opcode self._operand: Optional[Operand] = None + self.debug_info = debug_info if operand is not None: self.operand = operand @@ -160,6 +166,14 @@ def __str__(self) -> str: else: return f"{self._opcode.name} {self._operand:#x}" + def __eq__(self, other: Instruction) -> bool: + def normalize(x): + return (x & 0xFFFFFFFF) if type(x) == int and x < 0 else x + + return self.opcode == other.opcode and normalize(self._operand) == normalize( + other._operand + ) + @property def opcode(self) -> Opcode: return self._opcode diff --git a/quatch/_memory.py b/quatch/_memory.py index c311799..da7328e 100644 --- a/quatch/_memory.py +++ b/quatch/_memory.py @@ -52,7 +52,7 @@ def __getitem__(self, key): if isinstance(key, int): key = self._check_index(key) region = self.region_at(key) - if region is None or region.contents is None: + if region.contents is None: return 0 else: return region.contents[key - region.begin] @@ -60,20 +60,13 @@ def __getitem__(self, key): elif isinstance(key, slice): key = self._check_slice(key) result = bytearray() - position = key.start for region in self.regions_overlapping(key.start, key.stop): - # gaps caused by align() should be filled with zeros - result.extend(b"\x00" * (region.begin - position)) - position = region.end - begin = max(0, key.start - region.begin) end = region.size - max(0, (region.end - key.stop)) if region.contents is None: result.extend(b"\x00" * (end - begin)) else: result.extend(region.contents[begin:end]) - - result.extend(b"\x00" * (key.stop - position)) return result else: @@ -96,8 +89,9 @@ def __setitem__(self, key, value): if isinstance(key, int): key = self._check_index(key) region = self.region_at(key) - if region is None or region.contents is None: - raise IndexError("cannot assign to padding or BSS") + if region.contents is None: + if value != 0: + raise ValueError("cannot assign nonzero data to BSS") else: region.contents[key - region.begin] = value @@ -109,33 +103,17 @@ def __setitem__(self, key, value): if key.stop <= key.start: return - regions = self.regions_overlapping(key.start, key.stop) - writable = True - - # check for gaps between key.start and key.stop - position = key.start - for region in regions: - if region.begin > position or region.contents is None: - break - position = region.end - - if position < key.stop: - writable = False - - # if there were no regions found and the slice isn't empty then the whole - # thing is padding - if len(regions) == 0 and key.start < key.stop: - writable = False - - if not writable: - raise IndexError("cannot assign to padding or BSS") - - for region in regions: + for region in self.regions_overlapping(key.start, key.stop): src_begin = max(0, region.begin - key.start) src_end = min(len(value), region.end - key.start) dst_begin = max(0, key.start - region.begin) dst_end = min(region.size, key.stop - region.begin) - region.contents[dst_begin:dst_end] = value[src_begin:src_end] + data = value[src_begin:src_end] + if region.tag == RegionTag.BSS: + if not all(byte == 0 for byte in data): + raise ValueError("cannot assign nonzero data to BSS") + else: + region.contents[dst_begin:dst_end] = data else: raise TypeError("indices must be integers or slices") @@ -213,7 +191,9 @@ def align(self, alignment: int) -> None: Does nothing if len(self) is already a multiple of alignment. """ - self._size = align(self._size, alignment) + padding_size = align(self._size, alignment) - self._size + if padding_size != 0: + self.add_region(RegionTag.BSS, size=padding_size) def regions_with_tag(self, tag: RegionTag) -> Iterator[Region]: """Find all regions with a given tag.""" diff --git a/quatch/_q3asm.py b/quatch/_q3asm.py index 9f4d3a9..af26eca 100644 --- a/quatch/_q3asm.py +++ b/quatch/_q3asm.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with Quatch; If not, see . +from collections import namedtuple from ._instruction import Instruction as Ins, Opcode as Op from ._util import align, pad @@ -125,17 +126,100 @@ def __init__(self, image=None, segment_base=0): class Symbol: - def __init__(self, segment, value): + def __init__(self, segment, value, type): self.segment = segment self.value = value + self.type = type + + +LocalSymbol = namedtuple("LocalSymbol", ["name", "refinc"]) class AssemblerError(Exception): pass +FRAME_EXTRA_SIZE = 8 + + class Assembler: - def assemble(self, input_files, code_base=0, data_base=0, symbols={}): + def __init__(self, suppress_missing_symbols=False): + self.suppress_missing_symbols = suppress_missing_symbols + self._locals = {} + self.local_symbols = {} + + def my_assemble( + self, + input_files, + symbols={}, + ): + self.symbols = {} + + self.file = "unknown" + self.line = 0 + + # provided symbols should be relative to 0, no matter what code_base + # and data_base are + fake_segment = Segment() + for name, sym in symbols.items(): + self.symbols[name] = Symbol(fake_segment, sym["value"], sym["type"]) + + self.current_args = 0 + self.current_locals = 0 + self.current_arg_offset = 0 + rets = [] + for pass_number in range(2): + self.pass_number = pass_number + self.segments = {name: Segment() for name in ("code", "data", "lit", "bss")} + for current_file_index, (filename, *base_map) in enumerate(input_files): + base_map = base_map[0] if base_map else {} + + # clear segments, otherwise we keep appending the same dict + # TODO solve this in a better way or something + old_segs = self.segments + self.segments = {} + + for section in ("code", "data", "lit", "bss"): + if f"{section}_base" in base_map: + segment_base = base_map[f"{section}_base"] + self.segments[section] = Segment(segment_base=segment_base) + if section == "data" and segment_base == 0: + # q3asm reserves address 0 for nullptrs + self.segments[section].image += b"\x00\x00\x00\x00" + else: + seg = old_segs[section] + self.segments[section] = Segment( + segment_base=seg.segment_base + len(seg.image) + ) + self.segments["code"].image = [] # TODO idk + self.current_file_index = current_file_index + self.comments = [] + with open(filename) as f: + for line in f: + self.segments["code"].image.extend(self._assemble_line(line)) + if pass_number == 1: + rets.append(self.segments) + + # convert symbol values to their actual addresses + symbols = { + name: { + "value": symbol.value + symbol.segment.segment_base, + "type": symbol.type, + } + for name, symbol in self.symbols.items() + } + return rets, symbols + + def assemble( + self, + input_files, + code_base=0, + data_base=0, + lit_base=None, + bss_base=None, + pad_segments=True, + symbols={}, + ): self.segments = {name: Segment() for name in ("code", "data", "lit", "bss")} data = self.segments["data"] lit = self.segments["lit"] @@ -158,8 +242,12 @@ def assemble(self, input_files, code_base=0, data_base=0, symbols={}): for pass_number in range(2): self.pass_number = pass_number data.segment_base = data_base - lit.segment_base = data_base + len(data.image) - bss.segment_base = lit.segment_base + len(lit.image) + lit.segment_base = ( + lit_base if lit_base is not None else data_base + len(data.image) + ) + bss.segment_base = ( + bss_base if bss_base is not None else lit.segment_base + len(lit.image) + ) for seg in self.segments: self.segments[seg].image = bytearray() @@ -175,8 +263,9 @@ def assemble(self, input_files, code_base=0, data_base=0, symbols={}): ) ) - for seg in self.segments: - self.segments[seg].image = pad(self.segments[seg].image, 4) + if pad_segments: + for seg in self.segments: + self.segments[seg].image = pad(self.segments[seg].image, 4) # convert symbol values to their actual addresses symbols = { @@ -186,7 +275,13 @@ def assemble(self, input_files, code_base=0, data_base=0, symbols={}): return instructions, self.segments, symbols - def _assemble_line(self, line, address): + def get_debug_info(self): + debug_info = "".join(self.comments) + self.comments = [] + return debug_info + + def _assemble_line(self, line): + address = len(self.segments["code"].image) # TODO idk tokens = line.split() if len(tokens) == 0: return [] @@ -218,41 +313,66 @@ def _assemble_line(self, line, address): else: operand = None - return [Ins(opcode, operand)] + return [Ins(opcode, operand, debug_info=self.get_debug_info())] elif tokens[0].startswith("CALL"): self.current_arg_offset = 0 - return [Ins(Op.CALL)] + return [Ins(Op.CALL, debug_info=self.get_debug_info())] elif tokens[0].startswith("ARG"): self.current_arg_offset += 4 - return [Ins(Op.ARG, 8 + self.current_arg_offset - 4)] + return [ + Ins( + Op.ARG, + 8 + self.current_arg_offset - 4, + debug_info=self.get_debug_info(), + ) + ] elif tokens[0].startswith("RET"): - return [Ins(Op.LEAVE, 8 + self.current_locals + self.current_args)] + return [ + Ins( + Op.LEAVE, + 8 + self.current_locals + self.current_args, + debug_info=self.get_debug_info(), + ) + ] elif tokens[0].startswith("pop"): - return [Ins(Op.POP)] + return [Ins(Op.POP, debug_info=self.get_debug_info())] elif tokens[0].startswith("ADDRF"): offset = self._parse_expression(tokens[1]) offset += 16 + self.current_args + self.current_locals - return [Ins(Op.LOCAL, offset)] + return [Ins(Op.LOCAL, offset, debug_info=self.get_debug_info())] elif tokens[0].startswith("ADDRL"): - offset = self._parse_expression(tokens[1]) + 8 + self.current_args - return [Ins(Op.LOCAL, offset)] + offset = ( + self._parse_expression(tokens[1]) + FRAME_EXTRA_SIZE + self.current_args + ) + return [Ins(Op.LOCAL, offset, debug_info=self.get_debug_info())] elif tokens[0] == "proc": self._define_symbol(tokens[1], address) self.current_locals = align(int(tokens[2]), 4) self.current_args = align(int(tokens[3]), 4) - return [Ins(Op.ENTER, 8 + self.current_locals + self.current_args)] + self._store_locals(address) + return [ + Ins( + Op.ENTER, + 8 + self.current_locals + self.current_args, + debug_info=self.get_debug_info(), + ) + ] elif tokens[0] == "endproc": return [ - Ins(Op.PUSH), - Ins(Op.LEAVE, 8 + self.current_locals + self.current_args), + Ins(Op.PUSH, debug_info=self.get_debug_info()), + Ins( + Op.LEAVE, + 8 + self.current_locals + self.current_args, + debug_info=self.get_debug_info(), + ), ] elif tokens[0] == "address": @@ -268,7 +388,9 @@ def _assemble_line(self, line, address): elif tokens[0] == "align": alignment = int(tokens[1]) - self.current_segment.image = pad(self.current_segment.image, alignment) + x = self.current_segment.segment_base + len(self.current_segment.image) + x = align(x, alignment) - x + self.current_segment.image.extend([0] * x) elif tokens[0] == "skip": size = int(tokens[1]) @@ -281,7 +403,9 @@ def _assemble_line(self, line, address): self._hack_to_segment(self.segments["lit"]) elif size == 4: self._hack_to_segment(self.segments["data"]) - self.current_segment.image += value.to_bytes(size, "little") + self.current_segment.image += value.to_bytes( + size, "little", signed=(value < 0) + ) elif tokens[0].startswith("LABEL"): if self.current_segment == self.segments["code"]: @@ -299,7 +423,11 @@ def _assemble_line(self, line, address): pass elif tokens[0].startswith(";"): - pass + comment = line[1:] + self.comments.append(comment[comment.index(":") + 1 :]) + + elif tokens[0] == "local": + self._remember_local(int(tokens[1]), tokens[2], float(tokens[3])) else: self._error("syntax error") @@ -341,7 +469,11 @@ def _define_symbol(self, name, value): if name.startswith("$"): name += f"_{self.current_file_index}" - self.symbols[name] = Symbol(self.current_segment, value) + self.symbols[name] = Symbol( + self.current_segment, + value, + "code" if self.current_segment == self.segments["code"] else "data", + ) self.last_symbol = self.symbols[name] def _lookup_symbol(self, name): @@ -352,11 +484,28 @@ def _lookup_symbol(self, name): name += f"_{self.current_file_index}" if name not in self.symbols: + if self.suppress_missing_symbols: + return 0xC0DEDA7A self._error(f"symbol {name} undefined") s = self.symbols[name] return s.segment.segment_base + s.value + def _store_locals(self, address): + if self.pass_number == 1: + self.local_symbols[self.current_segment.segment_base + address] = { + k + FRAME_EXTRA_SIZE + self.current_args: v + for k, v in self._locals.items() + } + self._locals = {} + + def _remember_local(self, offset, name, ref_inc): + if self.pass_number == 1 and self.current_segment == self.segments["code"]: + if offset in self._locals: + self._locals[offset].append(LocalSymbol(name, ref_inc)) + else: + self._locals[offset] = [LocalSymbol(name, ref_inc)] + def _hack_to_segment(self, segment): if self.current_segment != segment: self.current_segment = segment diff --git a/quatch/_qvm.py b/quatch/_qvm.py index 2aed090..9ce6b3c 100644 --- a/quatch/_qvm.py +++ b/quatch/_qvm.py @@ -26,11 +26,11 @@ import struct import tempfile from collections.abc import Iterable, Mapping -from typing import Optional, Union +from typing import Any, List, NamedTuple, Optional, Union from ._compile import compile_c_file, CompilerError from ._instruction import assemble, disassemble, Instruction as Ins, Opcode as Op from ._memory import Memory, RegionTag -from ._q3asm import Assembler, AssemblerError +from ._q3asm import Assembler, AssemblerError, Segment from ._util import crc32, forge_crc32, pad @@ -39,6 +39,11 @@ HEADER_SIZE = struct.calcsize(HEADER_FORMAT) +class CompilationResult(NamedTuple): + output: str + segments: dict[str, Segment] + + class InitSymbolError(Exception): pass @@ -60,7 +65,12 @@ class Qvm: symbols: A dictionary mapping symbol names to addresses. """ - def __init__(self, path: str, symbols: Mapping[str, int] = None) -> None: + def __init__( + self, + path: str, + code_symbols: Mapping[str, int] = None, + data_symbols: Mapping[str, int] = None, + ) -> None: """Initialize a Qvm from a .qvm file. A mapping from names to addresses may be provided in symbols. Anything defined @@ -73,7 +83,7 @@ def __init__(self, path: str, symbols: Mapping[str, int] = None) -> None: code_offset, code_length, data_offset, - self._orignal_data_length, + self._original_data_length, self._original_lit_length, bss_length, ) = struct.unpack(HEADER_FORMAT, f.read(HEADER_SIZE)) @@ -92,14 +102,22 @@ def __init__(self, path: str, symbols: Mapping[str, int] = None) -> None: bss_length -= STACK_SIZE f.seek(data_offset) - self.add_data(f.read(self._orignal_data_length)) + self.add_data(f.read(self._original_data_length)) self.add_lit(f.read(self._original_lit_length)) self.add_bss(bss_length) f.seek(0) self._original_crc = crc32(f.read()) - self.symbols = dict(symbols or {}) + self.symbols = {} + if code_symbols: + for name, value in code_symbols.items(): + self.symbols[name] = {"value": value, "type": "code"} + if data_symbols: + for name, value in data_symbols.items(): + if name in self.symbols: + raise ValueError(f"Symbol {name} already defined") + self.symbols[name] = {"value": value, "type": "data"} self._calls = collections.defaultdict(list) for i in range(len(self.instructions) - 1): @@ -107,7 +125,11 @@ def __init__(self, path: str, symbols: Mapping[str, int] = None) -> None: if first.opcode == Op.CONST and second.opcode == Op.CALL: self._calls[first.operand].append(i) - def write(self, path: str, forge_crc: bool = False) -> None: + self.local_symbols = {} + + def write( + self, path: str, map_path: Optional[str] = None, forge_crc: bool = False + ) -> None: """Write a .qvm file. If forge_crc is True, the resulting file will have the same CRC-32 checksum as @@ -128,12 +150,12 @@ def write(self, path: str, forge_crc: bool = False) -> None: data_offset = f.tell() f.write( - self.memory[: self._orignal_data_length + self._original_lit_length] + self.memory[: self._original_data_length + self._original_lit_length] ) bss_length = ( len(self.memory) - - self._orignal_data_length + - self._original_data_length - self._original_lit_length + STACK_SIZE ) @@ -147,7 +169,7 @@ def write(self, path: str, forge_crc: bool = False) -> None: code_offset, len(code), data_offset, - self._orignal_data_length, + self._original_data_length, self._original_lit_length, bss_length, ) @@ -160,6 +182,15 @@ def write(self, path: str, forge_crc: bool = False) -> None: # section since nobody should be using address 0 forge_crc32(mm, data_offset, self._original_crc) + if not map_path: + return + with open(map_path, "w") as f: + for name, sym in self.symbols.items(): + if name.startswith("$"): + continue + value = sym["value"] & 0xFFFFFFFF + f.write(f"{0 if sym['type'] == 'code' else 1} {value:8x} {name}\n") + def add_data(self, data: bytes, alignment: int = 4) -> int: """Add data to the DATA section and return its address. @@ -188,16 +219,13 @@ def add_code(self, instructions: Iterable[Ins]) -> int: return address def add_c_code( - self, code: str, include_dirs: Optional[Iterable[str]] = None - ) -> str: + self, + code: str, + **kwargs: Optional[Any], + ) -> CompilationResult: """Compile a string of C code and add it to the Qvm. - Additional search paths for include files can be specified in include_dirs. - - Compilation errors will cause a CompilerError exception to be raised with the - error message. - - Returns the compiler's standard output/error. + See add_c_files for other arguments. """ c_file = tempfile.NamedTemporaryFile(suffix=".c", delete=False) try: @@ -206,29 +234,108 @@ def add_c_code( # this must be closed on windows or lcc won't be able to open it c_file.close() - return self.add_c_file(c_file.name, include_dirs=include_dirs) + return self.add_c_file(c_file.name, **kwargs) finally: c_file.close() with contextlib.suppress(FileNotFoundError): os.remove(c_file.name) def add_c_file( - self, path: str, include_dirs: Optional[Iterable[str]] = None - ) -> str: + self, + path: str, + **kwargs: Optional[Any], + ) -> CompilationResult: """Compile a C file and add the code to the Qvm. + See add_c_files for other arguments. + """ + return self.add_c_files([path], **kwargs) + + def my_add_c_files( + self, + paths: Iterable[str], + include_dirs: Optional[Iterable[str]] = None, + additional_cflags: Optional[List[str]] = None, + suppress_missing_symbols: Optional[bool] = False, + dump_stack: Optional[bool] = False, + ) -> CompilationResult: + """Compile C files and add the code to the Qvm. + Additional search paths for include files can be specified in include_dirs. Compilation errors will cause a CompilerError exception to be raised with the error message. - Returns the compiler's standard output/error. + Returns a CompilationResult with the compiler's standard output/error and + segments containing the compiled code and data. """ - return self.add_c_files([path], include_dirs=include_dirs) + asm_files = [] + output = [] + + try: + for (path, *rest) in paths: + asm_file = tempfile.NamedTemporaryFile(suffix=".asm", delete=False) + asm_files.append((asm_file, *rest)) + + # this must be closed on windows or lcc won't be able to open it + asm_file.close() + if dump_stack: + if additional_cflags: + additional_cflags += ["-Wf-dump-stack"] + else: + additional_cflags = ["-Wf-dump-stack"] + output.append( + compile_c_file( + path, + asm_file.name, + include_dirs=include_dirs, + additional_args=additional_cflags, + ) + ) + + self.memory.align(4) + + assembler = Assembler(suppress_missing_symbols) + file_segments, symbols = assembler.my_assemble( + [(asm_file.name, *rest) for (asm_file, *rest) in asm_files], + symbols=self.symbols, + ) + self.local_symbols.update(assembler.local_symbols) + + for segments in file_segments: + code_base = segments["code"].segment_base + code_data = segments["code"].image + self.instructions[code_base : code_base + len(code_data)] = code_data + + for region in ("data", "lit", "bss"): + segment = segments[region] + segment_base, segment_data = segment.segment_base, segment.image + self.memory[ + segment_base : segment_base + len(segment_data) + ] = segment_data + + self.symbols.update(symbols) + return [CompilationResult(i, x) for i, x in zip(output, file_segments)] + + except AssemblerError as e: + raise CompilerError(str(e)) from None + + finally: + for (asm_file, *rest) in asm_files: + asm_file.close() + with contextlib.suppress(FileNotFoundError): + os.remove(asm_file.name) def add_c_files( - self, paths: Iterable[str], include_dirs: Optional[Iterable[str]] = None - ) -> str: + self, + paths: Iterable[str], + include_dirs: Optional[Iterable[str]] = None, + code_base: Optional[int] = None, + data_base: Optional[int] = None, + bss_base: Optional[int] = None, + lit_base: Optional[int] = None, + pad_segments: Optional[bool] = True, + ) -> CompilationResult: """Compile C files and add the code to the Qvm. Additional search paths for include files can be specified in include_dirs. @@ -236,7 +343,8 @@ def add_c_files( Compilation errors will cause a CompilerError exception to be raised with the error message. - Returns the compiler's standard output/error. + Returns a CompilationResult with the compiler's standard output/error and + segments containing the compiled code and data. """ asm_files = [] output = "" @@ -256,19 +364,43 @@ def add_c_files( assembler = Assembler() instructions, segments, symbols = assembler.assemble( [asm_file.name for asm_file in asm_files], - code_base=len(self.instructions), - data_base=len(self.memory), + code_base=code_base + if code_base is not None + else len(self.instructions), + data_base=data_base if data_base is not None else len(self.memory), + lit_base=lit_base, + bss_base=bss_base, + pad_segments=pad_segments, symbols=self.symbols, ) - self.instructions.extend(instructions) - - self.add_data(segments["data"].image) - self.add_lit(segments["lit"].image) - self.add_bss(len(segments["bss"].image)) + if code_base is not None: + self.instructions[ + code_base : code_base + len(instructions) + ] = instructions + else: + self.instructions.extend(instructions) + + data = segments["data"].image + if data_base is not None: + self.memory[data_base : data_base + len(data)] = data + else: + self.add_data(data) + + lit = segments["lit"].image + if lit_base is not None: + self.memory[lit_base : lit_base + len(lit)] = lit + else: + self.add_lit(lit) + + bss = segments["bss"].image + if bss_base is not None: + self.memory[bss_base : bss_base + len(bss)] = bss + else: + self.add_bss(len(bss)) self.symbols.update(symbols) - return output + return CompilationResult(output, instructions, segments) except AssemblerError as e: raise CompilerError(str(e)) from None @@ -280,9 +412,16 @@ def add_c_files( os.remove(asm_file.name) def _add_data_init_code(self) -> None: + if ( + len(list(self.memory.regions_with_tag(RegionTag.DATA))) == 1 + and len(list(self.memory.regions_with_tag(RegionTag.LIT))) == 1 + ): + return + for init_name in ("G_InitGame", "CG_Init", "UI_Init"): original_init = self.symbols.get(init_name) if original_init is not None: + original_init = original_init["value"] break if original_init is None: @@ -304,7 +443,7 @@ def _add_data_init_code(self) -> None: begin, end = region.begin, region.end # skip .qvm's data section - if (begin, end) == (0, self._orignal_data_length): + if (begin, end) == (0, self._original_data_length): continue for address in range(begin, end, 4): @@ -324,8 +463,8 @@ def _add_data_init_code(self) -> None: # skip .qvm's lit section if (begin, end) == ( - self._orignal_data_length, - self._orignal_data_length + self._original_lit_length, + self._original_data_length, + self._original_data_length + self._original_lit_length, ): continue @@ -374,10 +513,10 @@ def replace_calls(self, old: Union[str, int], new: Union[str, int]) -> int: Returns the number of calls replaced. """ if isinstance(old, str): - old = self.symbols[old] + old = self.symbols[old]["value"] if isinstance(new, str): - new = self.symbols[new] + new = self.symbols[new]["value"] for call in self._calls[old]: self.instructions[call].operand = new