415 lines
15 KiB
Python
415 lines
15 KiB
Python
from dataclasses import dataclass
|
|
from enum import IntEnum
|
|
from itertools import pairwise
|
|
from typing import Union
|
|
|
|
from assembler.lexer import Token
|
|
from assembler.validator import ValidatorError
|
|
from assembler.vs.keywords import KW
|
|
from assembler.vs import opcodes
|
|
from collections import OrderedDict
|
|
|
|
|
|
class SourceType(IntEnum):
|
|
temporary = 0
|
|
input = 1
|
|
constant = 2
|
|
alt_temporary = 3
|
|
|
|
class SwizzleSelect(IntEnum):
|
|
x = 0
|
|
y = 1
|
|
z = 2
|
|
w = 3
|
|
zero = 4
|
|
one = 5
|
|
half = 6
|
|
unused = 7
|
|
|
|
@dataclass
|
|
class Source:
|
|
type: SourceType
|
|
absolute: bool
|
|
offset: int
|
|
swizzle_selects: tuple[SwizzleSelect, SwizzleSelect, SwizzleSelect, SwizzleSelect]
|
|
modifiers: tuple[bool, bool, bool, bool]
|
|
|
|
class DestinationType(IntEnum):
|
|
temporary = 0
|
|
a0 = 1
|
|
out = 2
|
|
out_repl_x = 3
|
|
alt_temporary = 4
|
|
input = 5
|
|
|
|
@dataclass
|
|
class Destination:
|
|
type: DestinationType
|
|
offset: int
|
|
write_enable: tuple[bool, bool, bool, bool]
|
|
|
|
@dataclass
|
|
class Instruction:
|
|
destination: Destination
|
|
saturation: bool
|
|
sources: list[Source]
|
|
opcode: Union[opcodes.VE, opcodes.ME, opcodes.MVE]
|
|
|
|
@dataclass
|
|
class DualMathVEOperation:
|
|
destination: Destination
|
|
saturation: bool
|
|
sources: list[Source]
|
|
opcode: opcodes.VE
|
|
|
|
class DualMathMEWriteEnable(IntEnum):
|
|
x = 0
|
|
y = 1
|
|
z = 2
|
|
w = 3
|
|
|
|
@dataclass
|
|
class DualMathMEDestination:
|
|
offset: int
|
|
write_enable: DualMathMEWriteEnable
|
|
|
|
@dataclass
|
|
class DualMathMESource:
|
|
type: SourceType
|
|
absolute: bool
|
|
offset: int
|
|
swizzle_selects: tuple[SwizzleSelect, SwizzleSelect]
|
|
modifiers: tuple[bool, bool]
|
|
|
|
@dataclass
|
|
class DualMathMEOperation:
|
|
destination: Destination
|
|
saturation: bool
|
|
sources: list[DualMathMESource]
|
|
opcode: opcodes.ME
|
|
|
|
@dataclass
|
|
class DualMathInstruction:
|
|
ve_operation: DualMathVEOperation
|
|
me_operation: DualMathMEOperation
|
|
|
|
def validate_opcode(opcode_keyword: Token):
|
|
if type(opcode_keyword.keyword) is opcodes.ME:
|
|
return opcode_keyword.keyword
|
|
elif type(opcode_keyword.keyword) is opcodes.VE:
|
|
return opcode_keyword.keyword
|
|
else:
|
|
raise ValidatorError("invalid opcode keyword", opcode_keyword)
|
|
|
|
def validate_identifier_number(token):
|
|
try:
|
|
return int(token.lexeme, 10)
|
|
except ValueError:
|
|
raise ValidatorError("invalid number", token)
|
|
|
|
def validate_destination_write_enable(write_enable_identifier):
|
|
we_chars_s = b"xyzw"
|
|
we_chars = {c: i for i, c in enumerate(we_chars_s)}
|
|
we = bytes(write_enable_identifier.lexeme).lower()
|
|
|
|
if not all(c in we_chars for c in we):
|
|
raise ParserError("invalid character in destination write enable", write_enable_identifier)
|
|
if not all(we_chars[a] < we_chars[b] for a, b in pairwise(we)) or len(set(we)) != len(we):
|
|
raise ParserError("misleading non-sequential destination write enable", write_enable_identifier)
|
|
|
|
we = set(we)
|
|
return tuple(c in we for c in we_chars_s)
|
|
|
|
def validate_destination(destination):
|
|
destination_type_keywords = OrderedDict([
|
|
(KW.temporary , (DestinationType.temporary , 128)), # 32
|
|
(KW.a0 , (DestinationType.a0 , 1 )), # ??
|
|
(KW.out , (DestinationType.out , 128)), # ?
|
|
(KW.out_repl_x , (DestinationType.out_repl_x , 128)), # ?
|
|
(KW.alt_temporary , (DestinationType.alt_temporary, 20 )), # 20
|
|
(KW.input , (DestinationType.input , 128)), # 32
|
|
])
|
|
if destination.type_keyword.keyword not in destination_type_keywords:
|
|
raise ValidatorError("invalid destination type keyword", destination.type_keyword)
|
|
type, max_offset = destination_type_keywords[destination.type_keyword.keyword]
|
|
offset = validate_identifier_number(destination.offset_identifier)
|
|
if offset >= max_offset:
|
|
raise ValidatorError("invalid offset value", source.offset_identifier)
|
|
|
|
write_enable = validate_destination_write_enable(destination.write_enable_identifier)
|
|
|
|
return Destination(
|
|
type,
|
|
offset,
|
|
write_enable
|
|
)
|
|
|
|
def parse_swizzle_lexeme(token):
|
|
swizzle_select_characters = OrderedDict([
|
|
(ord(b"x"), SwizzleSelect.x),
|
|
(ord(b"y"), SwizzleSelect.y),
|
|
(ord(b"z"), SwizzleSelect.z),
|
|
(ord(b"w"), SwizzleSelect.w),
|
|
(ord(b"0"), SwizzleSelect.zero),
|
|
(ord(b"1"), SwizzleSelect.one),
|
|
(ord(b"h"), SwizzleSelect.half),
|
|
(ord(b"_"), SwizzleSelect.unused),
|
|
])
|
|
lexeme = bytes(token.lexeme).lower()
|
|
swizzles = []
|
|
modifier = False
|
|
for c in lexeme:
|
|
if c == ord(b"-"):
|
|
modifier = True
|
|
else:
|
|
if c not in swizzle_select_characters:
|
|
raise ValueError(c)
|
|
swizzles.append((swizzle_select_characters[c], modifier))
|
|
modifier = False
|
|
return tuple(zip(*swizzles))
|
|
|
|
def validate_source(source, swizzle_select_length):
|
|
source_type_keywords = OrderedDict([
|
|
(KW.temporary , (SourceType.temporary , 128)), # 32
|
|
(KW.input , (SourceType.input , 128)), # 32
|
|
(KW.constant , (SourceType.constant , 256)), # 256
|
|
(KW.alt_temporary , (SourceType.alt_temporary, 20)), # 20
|
|
])
|
|
if source.type_keyword.keyword not in source_type_keywords:
|
|
raise ValidatorError("invalid source type keyword", source.type_keyword)
|
|
|
|
type, max_offset = source_type_keywords[source.type_keyword.keyword]
|
|
absolute = source.absolute
|
|
offset = validate_identifier_number(source.offset_identifier)
|
|
if offset >= max_offset:
|
|
raise ValidatorError("invalid offset value", source.offset_identifier)
|
|
try:
|
|
swizzle_selects, modifiers = parse_swizzle_lexeme(source.swizzle_identifier)
|
|
except ValueError:
|
|
raise ValidatorError("invalid source swizzle", source.swizzle_identifier)
|
|
|
|
assert len(swizzle_selects) == len(modifiers)
|
|
if len(swizzle_selects) != swizzle_select_length:
|
|
raise ValidatorError("invalid source swizzle", source.swizzle_identifier)
|
|
|
|
return Source(
|
|
type,
|
|
absolute,
|
|
offset,
|
|
swizzle_selects,
|
|
modifiers
|
|
)
|
|
|
|
def addresses_by_type(sources, source_type):
|
|
return set(int(source.offset)
|
|
for source in sources
|
|
if source.type == source_type)
|
|
|
|
def source_ix_with_type_reversed(sources, source_type):
|
|
for i, source in reversed(list(enumerate(sources))):
|
|
if source.type == source_type:
|
|
return i
|
|
assert False, (sources, source_type)
|
|
|
|
def validate_source_address_counts(sources_ast, sources, opcode):
|
|
temporary_address_count = len(addresses_by_type(sources, SourceType.temporary))
|
|
assert temporary_address_count >= 0 and temporary_address_count <= 3
|
|
assert type(opcode) in {opcodes.VE, opcodes.ME}
|
|
if temporary_address_count == 3:
|
|
if opcode == opcodes.VE_MULTIPLY_ADD:
|
|
opcode = opcodes.MACRO_OP_2CLK_MADD
|
|
elif opcode == opcodes.VE_MULTIPLYX2_ADD:
|
|
opcode = opcodes.MACRO_OP_2CLK_M2X_ADD
|
|
else:
|
|
raise ValidatorError("too many temporary addresses in non-macro operation(s)",
|
|
sources_ast[-1].offset_identifier)
|
|
|
|
constant_count = len(addresses_by_type(sources, SourceType.constant))
|
|
if constant_count > 1:
|
|
source_ix = source_ix_with_type_reversed(sources, SourceType.constant)
|
|
raise ValidatorError(f"too many constant addresses in operation(s); expected 1, have {constant_count}",
|
|
sources_ast[source_ix].offset_identifier)
|
|
|
|
input_count = len(addresses_by_type(sources, SourceType.input))
|
|
if input_count > 1:
|
|
source_ix = source_ix_with_type_reversed(sources, SourceType.input)
|
|
raise ValidatorError(f"too many input addresses in operation(s); expected 1, have {input_count}",
|
|
sources_ast[source_ix].offset_identifier)
|
|
|
|
alt_temporary_count = len(addresses_by_type(sources, SourceType.alt_temporary))
|
|
if alt_temporary_count > 1:
|
|
source_ix = source_ix_with_type_reversed(sources, SourceType.alt_temporary)
|
|
raise ValidatorError(f"too many alt temporary addresses in operation(s); expected 1, have {alt_temporary_count}",
|
|
sources_ast[source_ix].offset_identifier)
|
|
|
|
return opcode
|
|
|
|
def validate_instruction_inner(operation, opcode):
|
|
destination = validate_destination(operation.destination)
|
|
saturation = False
|
|
if operation.opcode_suffix_keyword is not None:
|
|
if operation.opcode_suffix_keyword.keyword is not KW.saturation:
|
|
raise ValidatorError("invalid opcode saturation suffix", operation.opcode_suffix_keyword)
|
|
saturation = True
|
|
if len(operation.sources) > 3:
|
|
raise ValidatorError("too many sources in operation", operation.sources[-1].type_keyword)
|
|
if len(operation.sources) != opcode.operand_count:
|
|
raise ValidatorError(f"incorrect number of source operands; expected {opcode.operand_count}", operation.sources[0].type_keyword)
|
|
|
|
sources = []
|
|
for source in operation.sources:
|
|
sources.append(validate_source(source, swizzle_select_length=4))
|
|
opcode = validate_source_address_counts(operation.sources, sources, opcode)
|
|
|
|
return Instruction(
|
|
destination,
|
|
saturation,
|
|
sources,
|
|
opcode
|
|
)
|
|
|
|
def validate_dual_math_ve_operation(operation, opcode):
|
|
destination = validate_destination(operation.destination)
|
|
saturation = False
|
|
if operation.opcode_suffix_keyword is not None:
|
|
if operation.opcode_suffix_keyword.keyword is not KW.saturation:
|
|
raise ValidatorError("invalid opcode saturation suffix", operation.opcode_suffix_keyword)
|
|
saturation = True
|
|
if len(operation.sources) > 2:
|
|
raise ValidatorError("too many sources in dual math VE operation", operation.sources[-1].type_keyword)
|
|
if opcode.operand_count > 2:
|
|
raise ValidatorError("3-operand opcode not valid in dual math VE operation", operation.sources[-1].type_keyword)
|
|
if len(operation.sources) != opcode.operand_count:
|
|
raise ValidatorError(f"incorrect number of source operands; expected {opcode.operand_count}", operation.sources[0].type_keyword)
|
|
|
|
sources = []
|
|
for source in operation.sources:
|
|
sources.append(validate_source(source, swizzle_select_length=4))
|
|
|
|
return DualMathVEOperation(
|
|
destination,
|
|
saturation,
|
|
sources,
|
|
opcode
|
|
)
|
|
|
|
def validate_dual_math_me_destination(destination):
|
|
if destination.type_keyword.keyword is not KW.alt_temporary:
|
|
raise ValidatorError("invalid dual math ME destination type keyword", destination.type_keyword)
|
|
offset = validate_identifier_number(destination.offset_identifier)
|
|
if offset >= 4:
|
|
raise ValidatorError("invalid dual math ME offset value", source.offset_identifier)
|
|
|
|
we = bytes(destination.write_enable_identifier.lexeme).lower()
|
|
if len(we) != 1:
|
|
raise ValidatorError("invalid dual math ME write enable", destination.write_enable_identifier)
|
|
we_chars = {
|
|
c: t for c, t in zip(b"xyzw", [
|
|
DualMathMEWriteEnable.x,
|
|
DualMathMEWriteEnable.y,
|
|
DualMathMEWriteEnable.z,
|
|
DualMathMEWriteEnable.w,
|
|
])
|
|
}
|
|
we_char = we[0]
|
|
if we_char not in we_chars:
|
|
ParserError("invalid dual math ME write enable", destination.write_enable_identifier)
|
|
|
|
write_enable = we_chars[we[0]]
|
|
|
|
return DualMathMEDestination(
|
|
offset,
|
|
write_enable,
|
|
)
|
|
|
|
def validate_dual_math_me_operation(operation, opcode):
|
|
destination = validate_dual_math_me_destination(operation.destination)
|
|
saturation = False
|
|
if operation.opcode_suffix_keyword is not None:
|
|
if operation.opcode_suffix_keyword.keyword is not KW.saturation:
|
|
raise ValidatorError("invalid opcode saturation suffix", operation.opcode_suffix_keyword)
|
|
saturation = True
|
|
if len(operation.sources) > 1:
|
|
raise ValidatorError("too many sources in dual math ME operation", operation.sources[-1].type_keyword)
|
|
if len(operation.sources) != opcode.operand_count:
|
|
raise ValidatorError(f"incorrect number of source operands; expected {opcode.operand_count}", operation.sources[0].type_keyword)
|
|
|
|
sources = []
|
|
for source in operation.sources:
|
|
sources.append(validate_source(source, swizzle_select_length=2))
|
|
|
|
return DualMathMEOperation(
|
|
destination,
|
|
saturation,
|
|
sources,
|
|
opcode,
|
|
)
|
|
|
|
def validate_dual_math_instruction(operations, _opcodes):
|
|
if type(_opcodes[0]) is opcodes.VE:
|
|
ve_operation_ast = operations[0]
|
|
ve_opcode = _opcodes[0]
|
|
me_operation_ast = operations[1]
|
|
me_opcode = _opcodes[1]
|
|
else:
|
|
ve_operation_ast = operations[1]
|
|
ve_opcode = _opcodes[1]
|
|
me_operation_ast = operations[0]
|
|
me_opcode = _opcodes[0]
|
|
|
|
ve_operation = validate_dual_math_ve_operation(ve_operation_ast, ve_opcode)
|
|
me_operation = validate_dual_math_me_operation(me_operation_ast, me_opcode)
|
|
|
|
all_sources_ast = ve_operation_ast.sources + me_operation_ast.sources
|
|
all_sources = ve_operation.sources + me_operation.sources
|
|
validate_opcode = validate_source_address_counts(all_sources_ast, all_sources, ve_operation.opcode)
|
|
assert validate_opcode == ve_operation.opcode
|
|
|
|
return DualMathInstruction(
|
|
ve_operation,
|
|
me_operation,
|
|
)
|
|
|
|
def validate_instruction(ins):
|
|
if len(ins.operations) > 2:
|
|
raise ValidatorError("too many operations in instruction", ins.operations[0].destination.type_keyword)
|
|
opcodes = [validate_opcode(operation.opcode_keyword) for operation in ins.operations]
|
|
opcode_types = set(type(opcode) for opcode in opcodes)
|
|
if len(opcode_types) != len(opcodes):
|
|
opcode_type, = opcode_types
|
|
raise ValidatorError(f"invalid dual math operation: too many opcodes of type {opcode_type}", ins.operations[0].opcode_keyword)
|
|
|
|
if len(opcodes) == 2:
|
|
return validate_dual_math_instruction(ins.operations, opcodes)
|
|
else:
|
|
assert len(opcodes) == 1, (opcodes, ins)
|
|
return validate_instruction_inner(ins.operations[0], opcodes[0])
|
|
|
|
if __name__ == "__main__":
|
|
from assembler.lexer import Lexer
|
|
from assembler.parser import ParserError
|
|
from assembler.vs.parser import Parser
|
|
from assembler.vs.keywords import find_keyword
|
|
from assembler.error import print_error
|
|
buf = b"""
|
|
out[0].xz = VE_MAD.SAT |temp[1].-y-_0-_| const[2].x_0_ const[2].x_0_ ;
|
|
"""
|
|
buf = b"""
|
|
out[0].xz = VE_MUL.SAT |temp[1].-y-_0-_| const[2].x_0_ ,
|
|
alt_temp[0].x = ME_SIN input[0].-y-_ ;
|
|
"""
|
|
|
|
lexer = Lexer(buf, find_keyword, emit_newlines=False, minus_is_token=False)
|
|
tokens = list(lexer.lex_tokens())
|
|
parser = Parser(tokens)
|
|
from pprint import pprint
|
|
try:
|
|
ins = parser.instruction()
|
|
pprint(validate_instruction(ins))
|
|
except ValidatorError as e:
|
|
print_error(None, buf, e)
|
|
raise
|
|
except ParserError as e:
|
|
print_error(None, buf, e)
|
|
raise
|