Files
ladybird/Meta/libjs_bytecode_def.py
Andreas Kling 003589db2d LibJS: Generate C++ bytecode instruction classes from a definition file
This commit adds a new Bytecode.def file that describes all the LibJS
bytecode instructions.

From this, we are able to generate the full declarations for all C++
bytecode instruction classes, as well as their serialization code.

Note that some of the bytecode compiler was updated since instructions
no longer have default constructor arguments.

The big immediate benefit here is that we lose a couple thousand lines
of hand-written C++ code. Going forward, this also allows us to do more
tooling for the bytecode VM, now that we have an authoritative
description of its instructions.

Key things to know about:

- Instructions can inherit from one another. At the moment, everything
  simply inherits from the base "Instruction".

- @terminator means the instruction terminates a basic block.

- @nothrow means the instruction cannot throw. This affects how the
  interpreter interacts with it.

- Variable-length instructions are automatically supported. Just put an
  array of something as the last field of the instruction.

- The m_length field is magical. If present, it will be populated with
  the full length of the instruction. This is used for variable-length
  instructions.
2025-11-21 09:46:03 +01:00

98 lines
2.5 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import field
from typing import List
from typing import Optional
@dataclass
class Field:
name: str
type: str
is_array: bool = False
@dataclass
class OpDef:
name: str
base: str
fields: List[Field] = field(default_factory=list)
is_terminator: bool = False # @terminator
is_nothrow: bool = False # @nothrow
def parse_bytecode_def(path: str) -> List[OpDef]:
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
ops: List[OpDef] = []
current: Optional[OpDef] = None
in_op = False
for raw_line in lines:
stripped = raw_line.strip()
if not stripped:
continue
if stripped.startswith("//") or stripped.startswith("#"):
continue
if stripped.startswith("op "):
if in_op:
raise RuntimeError("Nested op blocks are not allowed")
in_op = True
rest = stripped[len("op ") :].strip()
if "<" in rest:
name_part, base_part = rest.split("<", 1)
name = name_part.strip()
base = base_part.strip()
else:
name = rest.strip()
base = "Instruction"
current = OpDef(name=name, base=base)
continue
if stripped == "endop":
if not in_op or current is None:
raise RuntimeError("endop without corresponding op")
ops.append(current)
current = None
in_op = False
continue
if not in_op:
continue
if stripped.startswith("@"):
if stripped == "@terminator":
current.is_terminator = True
elif stripped == "@nothrow":
current.is_nothrow = True
continue
if ":" not in stripped:
raise RuntimeError(f"Malformed field line: {stripped!r}")
lhs, rhs = stripped.split(":", 1)
field_name = lhs.strip()
field_type = rhs.strip()
is_array = False
if field_type.endswith("[]"):
is_array = True
field_type = field_type[:-2].strip()
current.fields.append(Field(name=field_name, type=field_type, is_array=is_array))
if in_op or current is not None:
raise RuntimeError("Unclosed op block at end of file")
return ops
__all__ = [
"Field",
"OpDef",
"parse_bytecode_def",
]