adding tuples

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
This commit is contained in:
Jakub Doka 2024-12-19 23:08:36 +01:00
parent 969ea57e3f
commit 6e8eb059f6
No known key found for this signature in database
GPG key ID: C6E9A89936B8C143
8 changed files with 268 additions and 74 deletions

View file

@ -254,7 +254,7 @@ pub fn disasm<'a>(
|| global_offset > off + len
|| prev
.get(global_offset as usize)
.map_or(true, |&b| instr_from_byte(b).is_err());
.is_none_or(|&b| instr_from_byte(b).is_err());
has_oob |= local_has_oob;
let label = labels.get(&global_offset).unwrap();
if local_has_oob {

View file

@ -214,6 +214,14 @@ odher_pass := fn(t: Ty2): Ty2 {
}
```
#### tuples
```hb
main := fn(): uint {
tupl := .(1, 1)
return tupl[0] - tupl[1]
}
```
#### struct_scopes
```hb
$zr := 0
@ -475,7 +483,7 @@ arbitrary text
- `@embed(<string>)`: include relative file as an array of bytes
- `@inline(<func>, ...<args>)`: equivalent to `<func>(...<args>)` but function is guaranteed to inline, compiler will otherwise never inline
- `@len(<ty>)`: reports a length of the type of indexing purposes or length ot a string constant
- `@kindof(<ty>)`: gives an u8 integer describing the kind of type as an index to array `[Builtin, Struct, Enum, Union, Ptr, Slice, Opt, Func, Template, Global, Const, Module]`
- `@kindof(<ty>)`: gives an u8 integer describing the kind of type as an index to array `[Builtin, Struct, Tuple, Enum, Union, Ptr, Slice, Opt, Func, Template, Global, Const, Module]`
- `@Any()`: generic parameter based on inference, TBD: this will ake arguments in the future that restrict what is accepted
- `@error(...<expr>)`: emit compiler error, if reachable, and use arguments to construct a message, can display strings and types
- `@ChildOf(<ty>)`: returns the child type of the `<ty>`, works for pointers and optionals (`@ChildOf(?u8) == u8`)

View file

@ -8,7 +8,7 @@ use {
utils::{EntSlice, EntVec},
},
alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec},
core::{assert_matches::debug_assert_matches, mem, ops::Range, usize},
core::{assert_matches::debug_assert_matches, mem, ops::Range},
hbbytecode::{self as instrs, *},
reg::Reg,
};
@ -314,7 +314,7 @@ impl Backend for HbvmBackend {
&& self
.jump_relocs
.last()
.map_or(true, |&(r, _)| self.offsets[r as usize] as usize != self.code.len())
.is_none_or(|&(r, _)| self.offsets[r as usize] as usize != self.code.len())
{
self.code.truncate(self.code.len() - 5);
self.ret_relocs.pop();
@ -653,6 +653,7 @@ enum PLoc {
WideReg(Reg, u16),
Ref(Reg, u32),
}
impl PLoc {
fn reg(self) -> u8 {
match self {

View file

@ -804,6 +804,7 @@ impl Nodes {
}
}
#[expect(unused)]
fn init_loc_of(&self, def: Nid, types: &Types) -> Reg {
if self[def].kind == Kind::Arg {
let mut parama = ParamAlloc(0..11);
@ -821,8 +822,9 @@ impl Nodes {
255
}
#[expect(unused)]
fn use_reg_of(&self, def: Nid, usage: Nid) -> Reg {
if matches!(self[usage].kind, Kind::Return { .. }) {}
//if matches!(self[usage].kind, Kind::Return { .. }) {}
255
}
@ -852,7 +854,7 @@ impl<'a> Regalloc<'a> {
Self { nodes: ctx, tys, res }.run_low(special_count);
}
fn run_low(&mut self, special_count: usize) {
fn run_low(&mut self, #[expect(unused)] special_count: usize) {
self.res.general_bundles.clear();
self.res.node_to_reg.clear();
#[cfg(debug_assertions)]

View file

@ -2089,7 +2089,7 @@ pub enum Kind {
Call {
unreachable: bool,
func: ty::Func,
args: ty::Tuple,
args: ty::List,
},
// [ctrl]
Die,

View file

@ -15,9 +15,9 @@ use {
},
ty::{
self, Arg, ArrayLen, CompState, ConstData, EnumData, EnumField, FTask, FuncData,
GlobalData, Loc, Module, Offset, OffsetIter, OptLayout, Sig, StringRef, StructData,
StructField, SymKey, TemplateData, Tuple, TypeBase, TypeIns, Types, UnionData,
UnionField,
GlobalData, List, Loc, Module, Offset, OffsetIter, OptLayout, Sig, StringRef,
StructData, StructField, SymKey, TemplateData, TupleData, TypeBase, TypeIns, Types,
UnionData, UnionField,
},
utils::{BitSet, EntSlice, Vc},
Ident,
@ -657,7 +657,7 @@ impl<'a> Codegen<'a> {
let fuc = self.tys.ins.funcs.push(FuncData {
file,
sig: Sig { args: Tuple::empty(), ret },
sig: Sig { args: List::empty(), ret },
..Default::default()
});
@ -1274,10 +1274,37 @@ impl<'a> Codegen<'a> {
Some(Value::ptr(self.offset(bs.id, offset)).ty(f))
}
ty::Kind::Tuple(t) => {
let Kind::CInt { value: idx } = self.ci.nodes[idx.id].kind else {
return self.error(
index.pos(),
"field index needs to be known at compile time",
);
};
let Some((f, offset)) = OffsetIter::new(t, self.tys)
.into_iter(self.tys)
.nth(idx as _)
.map(|(&f, off)| (f, off))
else {
return self.error(
index.pos(),
fa!(
"struct '{}' has only `{}' fields, \
but index was '{}'",
self.ty_display(bs.ty),
self.tys.tuple_fields(t).len(),
idx
),
);
};
Some(Value::ptr(self.offset(bs.id, offset)).ty(f))
}
_ => self.error(
base.pos(),
fa!(
"cant index into '{}' which is not array nor slice",
"cant index into '{}' which is not array nor slice or tuple or struct",
self.ty_display(bs.ty)
),
),
@ -1581,14 +1608,51 @@ impl<'a> Codegen<'a> {
self.gen_call(func, args, true)
}
Expr::Tupl { pos, ty, fields, .. } => {
ctx.ty = ty
let ty = ty
.map(|ty| self.ty(ty))
.or(ctx.ty.map(|ty| self.tys.inner_of(ty).unwrap_or(ty)));
inference!(sty, ctx, self, pos, "struct or slice", "<struct_ty>.(...)");
.or(ctx.ty.map(|ty| self.tys.inner_of(ty).unwrap_or(ty)))
.map(ty::Id::expand);
match sty.expand() {
ty::Kind::Struct(s) => {
let mem = self.new_stack(pos, sty);
match ty {
None => {
let arg_base = self.tys.tmp.args.len();
let mut values = Vec::with_capacity(fields.len());
for field in fields {
let val = self.expr(field)?;
self.tys.tmp.args.push(val.ty);
self.ci.nodes.lock(val.id);
values.push(val);
}
let Some(fields) = self.tys.pack_args(arg_base) else {
return self.error(pos, "this tuple exceeded the reasonable limit");
};
let key = SymKey::Tuple(fields);
let ty::Kind::Tuple(tupl) = self
.tys
.syms
.get_or_insert(key, &mut self.tys.ins, |ins| {
ins.tuples.push(TupleData { fields, ..Default::default() }).into()
})
.expand()
else {
unreachable!()
};
let mem = self.new_stack(pos, tupl.into());
let mut offs = OffsetIter::new(tupl, self.tys);
for value in values {
let (ty, offset) = offs.next_ty(self.tys).unwrap();
let mem = self.offset(mem, offset);
self.ci.nodes.unlock(value.id);
self.store_mem(mem, ty, value.id);
}
Some(Value::ptr(mem).ty(tupl.into()))
}
Some(ty::Kind::Struct(s)) => {
let mem = self.new_stack(pos, s.into());
let mut offs = OffsetIter::new(s, self.tys);
for field in fields {
let Some((ty, offset)) = offs.next_ty(self.tys) else {
@ -1617,16 +1681,17 @@ impl<'a> Codegen<'a> {
(append them to the end of the constructor)"),
);
}
Some(Value::ptr(mem).ty(sty))
Some(Value::ptr(mem).ty(s.into()))
}
ty::Kind::Slice(s) => {
Some(ty::Kind::Slice(s)) => {
let slice = &self.tys.ins.slices[s];
let len = slice.len().unwrap_or(fields.len());
let elem = slice.elem;
let elem_size = self.tys.size_of(elem);
let aty = slice
.len()
.map_or_else(|| self.tys.make_array(elem, len as ArrayLen), |_| sty);
let aty = slice.len().map_or_else(
|| self.tys.make_array(elem, len as ArrayLen),
|_| s.into(),
);
if len != fields.len() {
return self.error(
@ -1651,13 +1716,13 @@ impl<'a> Codegen<'a> {
Some(Value::ptr(mem).ty(aty))
}
_ => self.error(
Some(t) => self.error(
pos,
fa!(
"the {}type of the constructor is `{}`, \
but thats not a struct nor slice or array",
if ty.is_some() { "" } else { "inferred " },
self.ty_display(sty),
self.ty_display(t.compress()),
),
),
}
@ -2393,6 +2458,7 @@ impl<'a> Codegen<'a> {
| ty::Kind::Func(_)
| ty::Kind::Template(_)
| ty::Kind::Global(_)
| ty::Kind::Tuple(_)
| ty::Kind::Const(_)) => self.error(
pos,
fa!(
@ -4146,6 +4212,7 @@ mod tests {
loops;
pointers;
structs;
tuples;
struct_scopes;
enums;
unions;

View file

@ -38,9 +38,9 @@ pub type Offset = u32;
pub type Size = u32;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, PartialOrd, Ord)]
pub struct Tuple(pub u32);
pub struct List(pub u32);
impl Tuple {
impl List {
const LEN_BITS: u32 = 5;
const LEN_MASK: usize = Self::MAX_LEN - 1;
const MAX_LEN: usize = 1 << Self::LEN_BITS;
@ -104,6 +104,12 @@ impl ArgIter {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Id(NonZeroU32);
impl AsRef<Id> for Id {
fn as_ref(&self) -> &Id {
self
}
}
impl From<Id> for i64 {
fn from(value: Id) -> Self {
value.0.get() as _
@ -150,6 +156,7 @@ impl crate::ctx_map::CtxEntry for Id {
SymKey::Decl(gb.file.into(), gb.name)
}
Kind::Slice(s) => SymKey::Array(&ctx.slices[s]),
Kind::Tuple(t) => SymKey::Tuple(ctx.tuples[t].fields),
Kind::Module(_) | Kind::Builtin(_) => {
SymKey::Decl(Module::default().into(), Ident::INVALID)
}
@ -266,22 +273,19 @@ impl Id {
}
pub(crate) fn loc(&self, tys: &Types) -> Loc {
use Kind as K;
match self.expand() {
Kind::Opt(o)
K::Opt(o)
if let ty = tys.ins.opts[o].base
&& ty.loc(tys) == Loc::Reg
&& (ty.is_pointer() || tys.size_of(ty) < 8) =>
{
Loc::Reg
}
Kind::Ptr(_) | Kind::Enum(_) | Kind::Builtin(_) => Loc::Reg,
Kind::Struct(_) | Kind::Union(_) if tys.size_of(*self) == 0 => Loc::Reg,
Kind::Struct(_) | Kind::Union(_) | Kind::Slice(_) | Kind::Opt(_) => Loc::Stack,
c @ (Kind::Func(_)
| Kind::Global(_)
| Kind::Module(_)
| Kind::Const(_)
| Kind::Template(_)) => {
K::Ptr(_) | K::Enum(_) | K::Builtin(_) => Loc::Reg,
K::Struct(_) | K::Tuple(_) | K::Union(_) if tys.size_of(*self) == 0 => Loc::Reg,
K::Struct(_) | K::Tuple(_) | K::Union(_) | K::Slice(_) | K::Opt(_) => Loc::Stack,
c @ (K::Func(_) | K::Global(_) | K::Module(_) | K::Const(_) | K::Template(_)) => {
unreachable!("{c:?}")
}
}
@ -450,6 +454,7 @@ type_kind! {
pub enum Kind {
Builtin,
Struct,
Tuple,
Enum,
Union,
Ptr,
@ -514,25 +519,25 @@ impl<'a> Display<'a> {
impl core::fmt::Display for Display<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use Kind as TK;
match TK::from_ty(self.ty) {
TK::Module(idx) => {
use Kind as K;
match K::from_ty(self.ty) {
K::Module(idx) => {
f.write_str("@use(\"")?;
self.files[idx].path.fmt(f)?;
f.write_str(")[")?;
idx.fmt(f)?;
f.write_str("]")
}
TK::Builtin(ty) => f.write_str(to_str(ty)),
TK::Opt(ty) => {
K::Builtin(ty) => f.write_str(to_str(ty)),
K::Opt(ty) => {
f.write_str("?")?;
self.rety(self.tys.ins.opts[ty].base).fmt(f)
}
TK::Ptr(ty) => {
K::Ptr(ty) => {
f.write_str("^")?;
self.rety(self.tys.ins.ptrs[ty].base).fmt(f)
}
TK::Struct(idx) => {
K::Struct(idx) => {
let record = &self.tys.ins.structs[idx];
if record.name.is_null() {
f.write_str("[")?;
@ -554,7 +559,19 @@ impl core::fmt::Display for Display<'_> {
f.write_str(file.ident_str(record.name))
}
}
TK::Union(idx) => {
K::Tuple(idx) => {
f.write_str(".(")?;
for (i, &ty) in
self.tys.ins.args[self.tys.ins.tuples[idx].fields.range()].iter().enumerate()
{
if i != 0 {
f.write_str(", ")?;
}
self.rety(ty).fmt(f)?;
}
f.write_str(")")
}
K::Union(idx) => {
let record = &self.tys.ins.unions[idx];
if record.name.is_null() {
f.write_str("[")?;
@ -576,27 +593,27 @@ impl core::fmt::Display for Display<'_> {
f.write_str(file.ident_str(record.name))
}
}
TK::Enum(idx) => {
K::Enum(idx) => {
let enm = &self.tys.ins.enums[idx];
debug_assert!(!enm.name.is_null());
let file = &self.files[enm.file];
f.write_str(file.ident_str(enm.name))
}
TK::Func(idx) => {
K::Func(idx) => {
f.write_str("fn")?;
idx.fmt(f)
}
TK::Template(idx) => {
K::Template(idx) => {
f.write_str("fn")?;
idx.fmt(f)
}
TK::Global(idx) => {
K::Global(idx) => {
let global = &self.tys.ins.globals[idx];
let file = &self.files[global.file];
f.write_str(file.ident_str(global.name))?;
f.write_str(" (global)")
}
TK::Slice(idx) => {
K::Slice(idx) => {
let array = self.tys.ins.slices[idx];
f.write_str("[")?;
self.rety(array.elem).fmt(f)?;
@ -606,7 +623,7 @@ impl core::fmt::Display for Display<'_> {
}
f.write_str("]")
}
TK::Const(idx) => {
K::Const(idx) => {
let cnst = &self.tys.ins.consts[idx];
let file = &self.files[cnst.file];
f.write_str(file.ident_str(cnst.name))?;
@ -618,9 +635,10 @@ impl core::fmt::Display for Display<'_> {
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub enum SymKey<'a> {
Tuple(List),
Pointer(&'a PtrData),
Optional(&'a OptData),
Type(Id, Pos, Tuple),
Type(Id, Pos, List),
Decl(Id, Ident),
Array(&'a ArrayData),
Constant(&'a ConstData),
@ -628,7 +646,7 @@ pub enum SymKey<'a> {
#[derive(Clone, Copy, Default)]
pub struct Sig {
pub args: Tuple,
pub args: List,
pub ret: Id,
}
@ -722,7 +740,7 @@ pub struct TypeBase {
pub pos: Pos,
pub name: Ident,
pub field_start: u32,
pub captured: Tuple,
pub captured: List,
pub ast: ExprRef,
}
@ -764,6 +782,13 @@ pub struct StructData {
impl_deref!(StructData { base: TypeBase });
#[derive(Default)]
pub struct TupleData {
pub fields: List,
pub size: Cell<Size>,
pub align: Cell<u8>,
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct OptData {
pub base: Id,
@ -854,6 +879,7 @@ pub struct TypeIns {
pub ptrs: EntVec<Ptr, PtrData>,
pub opts: EntVec<Opt, OptData>,
pub slices: EntVec<Slice, ArrayData>,
pub tuples: EntVec<Tuple, TupleData>,
}
pub struct FTask {
@ -897,6 +923,7 @@ impl Types {
| Kind::Builtin(_)
| Kind::Ptr(_)
| Kind::Slice(_)
| Kind::Tuple(_)
| Kind::Opt(_) => utils::is_pascal_case,
Kind::Func(f)
if let &Expr::Closure { ret: &Expr::Ident { id, .. }, .. } =
@ -919,19 +946,19 @@ impl Types {
}
}
pub fn pack_args(&mut self, arg_base: usize) -> Option<Tuple> {
pub fn pack_args(&mut self, arg_base: usize) -> Option<List> {
let base = self.ins.args.len();
self.ins.args.extend(self.tmp.args.drain(arg_base..));
let needle = &self.ins.args[base..];
if needle.is_empty() {
return Some(Tuple::empty());
return Some(List::empty());
}
let len = needle.len();
// FIXME: maybe later when this becomes a bottleneck we use more
// efficient search (SIMD?, indexing?)
let sp = self.ins.args.windows(needle.len()).position(|val| val == needle).unwrap();
self.ins.args.truncate((sp + needle.len()).max(base));
Tuple::new(sp, len)
List::new(sp, len)
}
pub fn union_fields(&self, union: Union) -> &[UnionField] {
@ -1014,6 +1041,16 @@ impl Types {
self.ins.structs[stru].size.set(oiter.offset);
oiter.offset
}
Kind::Tuple(tuple) => {
if self.ins.tuples[tuple].size.get() != 0 {
return self.ins.tuples[tuple].size.get();
}
let mut oiter = OffsetIter::new(tuple, self);
while oiter.next(self).is_some() {}
self.ins.tuples[tuple].size.set(oiter.offset);
oiter.offset
}
Kind::Union(union) => {
if self.ins.unions[union].size.get() != 0 {
return self.ins.unions[union].size.get();
@ -1033,8 +1070,12 @@ impl Types {
self.size_of(base) + self.align_of(base)
}
}
_ if let Some(size) = ty.simple_size() => size,
ty => unimplemented!("size_of: {:?}", ty),
Kind::Ptr(_) | Kind::Builtin(_) => ty.simple_size().unwrap(),
Kind::Func(_)
| Kind::Template(_)
| Kind::Global(_)
| Kind::Const(_)
| Kind::Module(_) => unreachable!(),
}
}
@ -1066,6 +1107,15 @@ impl Types {
self.ins.structs[stru].align.set(align.try_into().unwrap());
align
}
Kind::Tuple(tuple) => {
if self.ins.tuples[tuple].align.get() != 0 {
return self.ins.tuples[tuple].align.get() as _;
}
let align =
self.tuple_fields(tuple).iter().map(|&f| self.align_of(f)).max().unwrap_or(1);
self.ins.tuples[tuple].align.set(align.try_into().unwrap());
align
}
Kind::Slice(arr) => {
let arr = &self.ins.slices[arr];
match arr.len {
@ -1073,7 +1123,14 @@ impl Types {
_ => self.align_of(arr.elem),
}
}
_ => self.size_of(ty).max(1),
Kind::Opt(opt) => self.align_of(self.ins.opts[opt].base),
Kind::Builtin(_) | Kind::Enum(_) | Kind::Ptr(_) => self.size_of(ty),
Kind::Func(_)
| Kind::Template(_)
| Kind::Global(_)
| Kind::Const(_)
| Kind::Module(_) => unreachable!(),
//_ => self.size_of(ty).max(1),
}
}
@ -1161,6 +1218,7 @@ impl Types {
| Kind::Template(_)
| Kind::Global(_)
| Kind::Module(_)
| Kind::Tuple(_)
| Kind::Const(_) => return None,
})
}
@ -1186,7 +1244,7 @@ impl Types {
self.type_base_of(ty).map(|b| b.parent)
}
pub fn captures_of<'a>(&self, ty: Id, file: &'a parser::Ast) -> Option<(&'a [Ident], Tuple)> {
pub fn captures_of<'a>(&self, ty: Id, file: &'a parser::Ast) -> Option<(&'a [Ident], List)> {
let base = self.type_base_of(ty)?;
let (Expr::Struct { captured, .. }
@ -1212,6 +1270,10 @@ impl Types {
let str = unsafe { core::mem::transmute::<&mut Vec<u8>, &mut String>(data) };
write!(str, "{}", Display::new(self, files, ty)).unwrap();
}
pub fn tuple_fields(&self, tuple: Tuple) -> &[Id] {
&self.ins.args[self.ins.tuples[tuple].fields.range()]
}
}
pub struct OptLayout {
@ -1220,17 +1282,57 @@ pub struct OptLayout {
pub payload_offset: Offset,
}
pub struct OffsetIter {
strct: Struct,
pub trait Agregate: Copy {
type Field: AsRef<Id> + 'static;
fn fields(self, tys: &Types) -> Range<usize>;
fn field_by_idx(tys: &Types, index: usize) -> &Self::Field;
fn align_override(self, _: &Types) -> Option<u8> {
None
}
}
impl Agregate for Tuple {
type Field = Id;
fn fields(self, tys: &Types) -> Range<usize> {
tys.ins.tuples[self].fields.range()
}
fn field_by_idx(tys: &Types, index: usize) -> &Self::Field {
&tys.ins.args[index]
}
}
impl Agregate for Struct {
type Field = StructField;
fn fields(self, tys: &Types) -> Range<usize> {
tys.struct_field_range(self)
}
fn field_by_idx(tys: &Types, index: usize) -> &Self::Field {
&tys.ins.struct_fields[index]
}
fn align_override(self, tys: &Types) -> Option<u8> {
tys.ins.structs[self].explicit_alignment
}
}
impl AsRef<Id> for StructField {
fn as_ref(&self) -> &Id {
&self.ty
}
}
pub struct OffsetIter<T> {
strct: T,
offset: Offset,
fields: Range<usize>,
}
impl OffsetIter {
pub fn new(strct: Struct, tys: &Types) -> Self {
Self { strct, offset: 0, fields: tys.struct_field_range(strct) }
}
impl OffsetIter<Struct> {
pub fn offset_of(tys: &Types, idx: Struct, field: &str) -> Option<(Offset, Id)> {
let field_id = tys.names.project(field)?;
OffsetIter::new(idx, tys)
@ -1238,25 +1340,33 @@ impl OffsetIter {
.find(|(f, _)| f.name == field_id)
.map(|(f, off)| (off, f.ty))
}
}
fn next<'a>(&mut self, tys: &'a Types) -> Option<(&'a StructField, Offset)> {
let stru = &tys.ins.structs[self.strct];
let field = &tys.ins.struct_fields[self.fields.next()?];
impl<T: Agregate> OffsetIter<T> {
pub fn new(strct: T, tys: &Types) -> Self {
Self { strct, offset: 0, fields: strct.fields(tys) }
}
let align = stru.explicit_alignment.map_or_else(|| tys.align_of(field.ty), |a| a as u32);
fn next<'a>(&mut self, tys: &'a Types) -> Option<(&'a T::Field, Offset)> {
let field = &T::field_by_idx(tys, self.fields.next()?);
let align = self
.strct
.align_override(tys)
.map_or_else(|| tys.align_of(*field.as_ref()), |a| a as u32);
self.offset = (self.offset + align - 1) & !(align - 1);
let off = self.offset;
self.offset += tys.size_of(field.ty);
self.offset += tys.size_of(*field.as_ref());
Some((field, off))
}
pub fn next_ty(&mut self, tys: &Types) -> Option<(Id, Offset)> {
let (field, off) = self.next(tys)?;
Some((field.ty, off))
Some((*field.as_ref(), off))
}
pub fn into_iter(mut self, tys: &Types) -> impl Iterator<Item = (&StructField, Offset)> {
pub fn into_iter(mut self, tys: &Types) -> impl Iterator<Item = (&T::Field, Offset)> {
core::iter::from_fn(move || self.next(tys))
}
}

View file

@ -0,0 +1,6 @@
main:
CP r1, r0
JALA r0, r31, 0a
code size: 22
ret: 0
status: Ok(())