From ada828fc3eaf9891e1bbb6503106d36ef53b6c8a Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Wed, 2 Oct 2024 11:21:55 -0600 Subject: wip lib9p bitfields --- lib9p/9P2000.txt | 35 +++++++- lib9p/9P2000.u.txt | 12 +-- lib9p/include/lib9p/_types.h | 6 +- lib9p/types.c | 6 +- lib9p/types.gen | 204 ++++++++++++++++++++++++++++++------------- 5 files changed, 184 insertions(+), 79 deletions(-) (limited to 'lib9p') diff --git a/lib9p/9P2000.txt b/lib9p/9P2000.txt index 5f93cdf..3f1f10a 100644 --- a/lib9p/9P2000.txt +++ b/lib9p/9P2000.txt @@ -30,8 +30,39 @@ d = "len[4] len*(dat[1])" # string (u16le `n`, then `n` bytes of UTF-8) s = "len[2] len*(utf8[1])" -# qid (TODO) -qid = "type[1] vers[4] path[8]" +bitfield qid_type 8 + 7/DIR + 6/APPEND + 5/EXCL + # QTMOUNT has been around in Plan 9 forever, but is + # undocumented, and is explicitly excluded from the 9P2000 + # draft RFC. As I understand it, QTMOUNT indicates that the + # file is mounted by the kernel as a 9P transport; that the + # kernel has a lock on doing I/O on it, so userspace can't do + # I/O on it. + 4/_PLAN9_MOUNT + 3/AUTH + # Fun historical fact: QTTMP was a relatively late addition to + # Plan 9, in 2003-12. + 2/TMP + #1/unused + FILE=0 + +# uniQue IDentification - "two files on the same server hierarchy are +# the same if and only if their qids are the same" +# +# - "path" is a unique uint64_t that does most of the work in the +# above statement about files being the same if their QIDs are the +# same; " If a file is deleted and recreated with the same name in +# the same directory, the old and new path components of the qids +# should be different" +# +# - "vers" "is a version number for a file; typically, it is +# incremented every time the file is modified. +# +# - "type" indicates "whether the file is a directory, append-only +# file, etc."; is an instance of the qid_type bitfield. +qid = "type[qid_type] vers[4] path[8]" # stat (TODO) stat = "stat_size[2]" diff --git a/lib9p/9P2000.u.txt b/lib9p/9P2000.u.txt index 1fa71fb..a1dbcfb 100644 --- a/lib9p/9P2000.u.txt +++ b/lib9p/9P2000.u.txt @@ -19,16 +19,8 @@ Tauth += "n_uname[4]" Rerror += "errno[4]" -# # qid.types -# QTDIR = 1<<7 -# QTAPPEND = 1<<6 -# QTEXCL = 1<<5 -# QTMOUNT = 1<<4 # been around forever, but undocumented? -# QTAUTH = 1<<3 -# QTTMP = 1<<2 # added to Plan 9 2003-12 -# QTSYMLINK = 1<<1 # .u -# QTFILE = 1<<0 -# +qid_type += 1/SYMLINK + # DMDIR = 1<<31 # DMAPPEND = 1<<30 # DMEXCL = 1<<29 diff --git a/lib9p/include/lib9p/_types.h b/lib9p/include/lib9p/_types.h index 94accc0..cd1ded5 100644 --- a/lib9p/include/lib9p/_types.h +++ b/lib9p/include/lib9p/_types.h @@ -30,9 +30,9 @@ struct lib9p_s { }; struct lib9p_qid { - uint8_t type; - uint32_t vers; - uint64_t path; + lib9p_qid_type_t type; + uint32_t vers; + uint64_t path; }; struct lib9p_stat { diff --git a/lib9p/types.c b/lib9p/types.c index 19ed322..4854ed4 100644 --- a/lib9p/types.c +++ b/lib9p/types.c @@ -344,7 +344,7 @@ static ALWAYS_INLINE bool checksize_s(struct _checksize_ctx *ctx) { } static ALWAYS_INLINE bool checksize_qid(struct _checksize_ctx *ctx) { - return checksize_1(ctx) + return checksize_qid_type(ctx) || checksize_4(ctx) || checksize_8(ctx); } @@ -571,7 +571,7 @@ static ALWAYS_INLINE void unmarshal_s(struct _unmarshal_ctx *ctx, struct lib9p_s static ALWAYS_INLINE void unmarshal_qid(struct _unmarshal_ctx *ctx, struct lib9p_qid *out) { memset(out, 0, sizeof(*out)); - unmarshal_1(ctx, &out->type); + unmarshal_qid_type(ctx, &out->type); unmarshal_4(ctx, &out->vers); unmarshal_8(ctx, &out->path); } @@ -860,7 +860,7 @@ static ALWAYS_INLINE bool marshal_s(struct _marshal_ctx *ctx, struct lib9p_s *va } static ALWAYS_INLINE bool marshal_qid(struct _marshal_ctx *ctx, struct lib9p_qid *val) { - return marshal_1(ctx, &val->type) + return marshal_qid_type(ctx, &val->type) || marshal_4(ctx, &val->vers) || marshal_8(ctx, &val->path); } diff --git a/lib9p/types.gen b/lib9p/types.gen index 9946e35..1a2d45a 100755 --- a/lib9p/types.gen +++ b/lib9p/types.gen @@ -8,7 +8,7 @@ import enum import os.path import re -from typing import Callable +from typing import Callable, Sequence # This strives to be "general-purpose" in that it just acts on the # *.txt inputs; but (unfortunately?) there are a few special-cases in @@ -32,6 +32,16 @@ class Atom(enum.Enum): return self.value +class Bitfield: + name: str + bits: list[str] + aliases: dict[str, str] + + @property + def static_size(self) -> int | None: + return int((len(self.bits) + 7) / 8) + + # `msgid/structname = "member1 member2..."` # `structname = "member1 member2..."` # `structname += "member1 member2..."` @@ -60,7 +70,7 @@ class Struct: class Member: cnt: str | None = None name: str - typ: Atom | Struct + typ: Atom | Bitfield | Struct ver: set[str] @property @@ -77,7 +87,10 @@ re_memberspec = ( def parse_members( - ver: str, env: dict[str, Atom | Struct], existing: list[Member], specs: str + ver: str, + env: dict[str, Atom | Bitfield | Struct], + existing: list[Member], + specs: str, ) -> list[Member]: ret = existing for spec in specs.split(): @@ -112,23 +125,26 @@ re_import = r"from\s+(?P\S+)\s+import\s+(?P\S+(?:\s*,\s*\S+)*)\s*" re_structspec = ( r'(?:(?P[0-9]+)/)?(?P\S+)\s*(?P\+?=)\s*"(?P[^"]*)"' ) -re_structspec_cont = r'"(?P[^"]*)"' +re_structspec_cont = r'\s+"(?P[^"]*)"' +re_bitfieldspec = r"bitfield\s+(?P\S+)\s+(?P[0-9]+)" +re_bitfieldspec_bit = r"(?:\s+|(?P\S+)\s*\+=\s*)(?P[0-9]+)/(?P\S+)" +re_bitfieldspec_alias = r"(?:\s+|(?P\S+)\s*\+=\s*)(?P\S+)=(?P.*)" def parse_file( - filename: str, get_include: Callable[[str], tuple[str, list[Struct]]] -) -> tuple[str, list[Struct]]: + filename: str, get_include: Callable[[str], tuple[str, list[Bitfield | Struct]]] +) -> tuple[str, list[Bitfield | Struct]]: version: str | None = None - env: dict[str, Atom | Struct] = { + env: dict[str, Atom | Bitfield | Struct] = { "1": Atom.u8, "2": Atom.u16, "4": Atom.u32, "8": Atom.u64, } with open(filename, "r") as fh: - prev: Struct | None = None + prev: Struct | Bitfield | None = None for line in fh: - line = line.split("#", 1)[0].strip() + line = line.split("#", 1)[0].rstrip() if not line: continue if m := re.fullmatch(re_version, line): @@ -138,17 +154,18 @@ def parse_file( elif m := re.fullmatch(re_import, line): if not version: raise SyntaxError("must have exactly 1 version line") - other_version, other_structs = get_include(m.group("file")) + other_version, other_typs = get_include(m.group("file")) for symname in m.group("syms").split(sep=","): symname = symname.strip() - for struct in other_structs: - if struct.name == symname or symname == "*": - if struct.msgid: - struct.msgver.add(version) - for member in struct.members: - if other_version in member.ver: - member.ver.add(version) - env[struct.name] = struct + for typ in other_typs: + if typ.name == symname or symname == "*": + if isinstance(typ, Struct): + if typ.msgid: + typ.msgver.add(version) + for member in typ.members: + if other_version in member.ver: + member.ver.add(version) + env[typ.name] = typ elif m := re.fullmatch(re_structspec, line): if not version: raise SyntaxError("must have exactly 1 version line") @@ -172,7 +189,7 @@ def parse_file( _struct = env[m.group("name")] if not isinstance(_struct, Struct): raise NameError( - f"Type {repr(m.group('name'))} is not a struct" + f"Type {repr(_struct.name)} is not a struct" ) struct = _struct struct.members = parse_members( @@ -180,27 +197,82 @@ def parse_file( ) prev = struct elif m := re.fullmatch(re_structspec_cont, line): - if not prev: - raise SyntaxError("continuation line must come after a struct line") + if not isinstance(prev, Struct): + raise SyntaxError( + "struct-continuation line must come after a struct line" + ) assert version prev.members = parse_members( version, env, prev.members, m.group("members") ) + elif m := re.fullmatch(re_bitfieldspec, line): + bf = Bitfield() + bf.name = m.group("name") + bf.bits = int(m.group("size")) * [""] + bf.aliases = {} + env[bf.name] = bf + prev = bf + elif m := re.fullmatch(re_bitfieldspec_bit, line): + if m.group("bitfield"): + if m.group("bitfield") not in env: + raise NameError(f"Unknown bitfield {repr(m.group('bitfield'))}") + _bf = env[m.group("bitfield")] + if not isinstance(_bf, Bitfield): + raise NameError(f"Type {repr(_bf.name)} is not a bitfield") + bf = _bf + else: + if not isinstance(prev, Bitfield): + raise SyntaxError( + "bitfield-continuation line must come after a bitfield line" + ) + bf = prev + bit = int(m.group("bit")) + name = m.group("name") + if bit < 0 or bit >= len(bf.bits): + raise ValueError(f"{bf.name}: bit {bit} is out-of-bounds") + if bf.bits[bit]: + raise ValueError(f"{bf.name}: bit {bit} already assigned") + if name in bf.aliases: + raise ValueError(f"{bf.name}: name {name} already assigned") + bf.bits[bit] = name + bf.aliases[name] = "" + elif m := re.fullmatch(re_bitfieldspec_alias, line): + if m.group("bitfield"): + if m.group("bitfield") not in env: + raise NameError(f"Unknown bitfield {repr(m.group('bitfield'))}") + _bf = env[m.group("bitfield")] + if not isinstance(_bf, Bitfield): + raise NameError(f"Type {repr(_bf.name)} is not a bitfield") + bf = _bf + else: + if not isinstance(prev, Bitfield): + raise SyntaxError( + "bitfield-continuation line must come after a bitfield line" + ) + bf = prev + name = m.group("name") + val = m.group("val") + if name in bf.aliases: + raise ValueError(f"{bf.name}: name {name} already assigned") + bf.aliases[name] = val else: raise SyntaxError(f"invalid line {repr(line)}") if not version: raise SyntaxError("must have exactly 1 version line") - structs = [x for x in env.values() if isinstance(x, Struct)] - return version, structs + typs = [x for x in env.values() if not isinstance(x, Atom)] + + return version, typs # Generate C ################################################################### -def c_typename(idprefix: str, typ: Atom | Struct) -> str: +def c_typename(idprefix: str, typ: Atom | Bitfield | Struct) -> str: match typ: case Atom(): return f"uint{typ.value*8}_t" + case Bitfield(): + return f"{idprefix}{typ.name}_t" case Struct(): if typ.msgid is not None: return f"struct {idprefix}msg_{typ.name}" @@ -227,7 +299,25 @@ def c_vercond(idprefix: str, versions: set[str]) -> str: ) -def gen_h(idprefix: str, versions: set[str], structs: list[Struct]) -> str: +def just_structs_all(typs: list[Bitfield | Struct]) -> Sequence[Struct]: + return list(typ for typ in typs if isinstance(typ, Struct)) + + +def just_structs_nonmsg(typs: list[Bitfield | Struct]) -> Sequence[Struct]: + return list(typ for typ in typs if isinstance(typ, Struct) and typ.msgid is None) + + +def just_structs_msg(typs: list[Bitfield | Struct]) -> Sequence[Struct]: + return list( + typ for typ in typs if isinstance(typ, Struct) and typ.msgid is not None + ) + + +def just_bitfields(typs: list[Bitfield | Struct]) -> Sequence[Bitfield]: + return list(typ for typ in typs if isinstance(typ, Bitfield)) + + +def gen_h(idprefix: str, versions: set[str], typs: list[Bitfield | Struct]) -> str: guard = "_LIB9P__TYPES_H_" ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ @@ -255,10 +345,7 @@ enum {idprefix}version {{ ret += """ /* non-message structs ********************************************************/ """ - for struct in structs: - if struct.msgid is not None: - continue - + for struct in just_structs_nonmsg(typs): all_the_same = len(struct.members) == 0 or all( m.ver == struct.members[0].ver for m in struct.members ) @@ -280,10 +367,8 @@ enum {idprefix}version {{ """ ret += f"enum {idprefix}msg_type {{ /* uint8_t */\n" - namewidth = max(len(msg.name) for msg in structs if msg.msgid is not None) - for msg in structs: - if msg.msgid is None: - continue + namewidth = max(len(msg.name) for msg in just_structs_msg(typs)) + for msg in just_structs_msg(typs): ret += f"\t{idprefix.upper()}TYP_{msg.name.ljust(namewidth)} = {msg.msgid}," if comment := c_vercomment(msg.msgver): ret += " " + comment @@ -292,10 +377,7 @@ enum {idprefix}version {{ ret += "\n" ret += f"const char *{idprefix}msg_type_str(enum {idprefix}msg_type);\n" - for msg in structs: - if msg.msgid is None: - continue - + for msg in just_structs_msg(typs): ret += "\n" if comment := c_vercomment(msg.msgver): ret += comment + "\n" @@ -324,7 +406,7 @@ enum {idprefix}version {{ return ret -def gen_c(idprefix: str, versions: set[str], structs: list[Struct]) -> str: +def gen_c(idprefix: str, versions: set[str], typs: list[Bitfield | Struct]) -> str: ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ #include @@ -362,9 +444,9 @@ const char *{idprefix}version_str(enum {idprefix}version ver) {{ static const char *msg_type_strs[0x100] = {{ """ id2name: dict[int, str] = {} - for msg in structs: - if msg.msgid is not None: - id2name[msg.msgid] = msg.name + for msg in just_structs_msg(typs): + assert msg.msgid + id2name[msg.msgid] = msg.name for n in range(0, 0x100): ret += '\t[0x{:02X}] = "{}",\n'.format(n, id2name.get(n, "0x{:02X}".format(n))) ret += "};\n" @@ -412,7 +494,7 @@ static ALWAYS_INLINE bool _checksize_list(struct _checksize_ctx *ctx, #define checksize_4(ctx) _checksize_net(ctx, 4) #define checksize_8(ctx) _checksize_net(ctx, 8) """ - for struct in structs: + for struct in just_structs_all(typs): inline = " ALWAYS_INLINE" if struct.msgid is None else " FLATTEN" argfn = used if struct.members else unused ret += "\n" @@ -495,7 +577,7 @@ static ALWAYS_INLINE void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *out) ctx->net_offset += 8; } """ - for struct in structs: + for struct in just_structs_all(typs): inline = " ALWAYS_INLINE" if struct.msgid is None else " FLATTEN" argfn = used if struct.members else unused ret += "\n" @@ -567,7 +649,7 @@ static ALWAYS_INLINE bool marshal_8(struct _marshal_ctx *ctx, uint64_t *val) { return false; } """ - for struct in structs: + for struct in just_structs_all(typs): inline = " ALWAYS_INLINE" if struct.msgid is None else " FLATTEN" argfn = used if struct.members else unused ret += "\n" @@ -616,14 +698,14 @@ struct _vtable_version _{idprefix}vtables[{c_verenum(idprefix, 'NUM')}] = {{ """ ret += f"\t[{c_verenum(idprefix, 'unknown')}] = {{ .msgs = {{\n" - for msg in structs: + for msg in just_structs_msg(typs): if msg.name in ["Tversion", "Rversion", "Rerror"]: # SPECIAL ret += f"\t\t_MSG({msg.name}),\n" ret += "\t}},\n" for ver in sorted(versions): ret += f"\t[{c_verenum(idprefix, ver)}] = {{ .msgs = {{\n" - for msg in structs: + for msg in just_structs_msg(typs): if ver not in msg.msgver: continue ret += f"\t\t_MSG({msg.name}),\n" @@ -638,32 +720,32 @@ struct _vtable_version _{idprefix}vtables[{c_verenum(idprefix, 'NUM')}] = {{ class Parser: - cache: dict[str, tuple[str, list[Struct]]] = {} + cache: dict[str, tuple[str, list[Bitfield | Struct]]] = {} - def parse_file(self, filename: str) -> tuple[str, list[Struct]]: + def parse_file(self, filename: str) -> tuple[str, list[Bitfield | Struct]]: filename = os.path.normpath(filename) if filename not in self.cache: - def get_include(other_filename: str) -> tuple[str, list[Struct]]: + def get_include(other_filename: str) -> tuple[str, list[Bitfield | Struct]]: return self.parse_file(os.path.join(filename, "..", other_filename)) self.cache[filename] = parse_file(filename, get_include) return self.cache[filename] - def all(self) -> tuple[set[str], list[Struct]]: + def all(self) -> tuple[set[str], list[Bitfield | Struct]]: ret_versions: set[str] = set() - ret_structs: dict[str, Struct] = {} - for version, structs in self.cache.values(): + ret_typs: dict[str, Bitfield | Struct] = {} + for version, typs in self.cache.values(): if version in ret_versions: raise ValueError(f"duplicate protocol version {repr(version)}") ret_versions.add(version) - for struct in structs: - if struct.name in ret_structs: - if struct != ret_structs[struct.name]: - raise ValueError(f"duplicate struct name {repr(struct.name)}") + for typ in typs: + if typ.name in ret_typs: + if typ != ret_typs[typ.name]: + raise ValueError(f"duplicate type name {repr(typ.name)}") else: - ret_structs[struct.name] = struct - return ret_versions, list(ret_structs.values()) + ret_typs[typ.name] = typ + return ret_versions, list(ret_typs.values()) if __name__ == "__main__": @@ -674,9 +756,9 @@ if __name__ == "__main__": parser = Parser() for txtname in sys.argv[1:]: parser.parse_file(txtname) - versions, structs = parser.all() + versions, typs = parser.all() outdir = os.path.normpath(os.path.join(sys.argv[0], "..")) with open(os.path.join(outdir, "include/lib9p/_types.h"), "w") as fh: - fh.write(gen_h("lib9p_", versions, structs)) + fh.write(gen_h("lib9p_", versions, typs)) with open(os.path.join(outdir, "types.c"), "w") as fh: - fh.write(gen_c("lib9p_", versions, structs)) + fh.write(gen_c("lib9p_", versions, typs)) -- cgit v1.2.3-2-g168b