adding unrolled loops, struct indexing and @len directive

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
This commit is contained in:
Jakub Doka 2024-12-16 13:20:47 +01:00
parent bfac81c807
commit b2be007ef0
No known key found for this signature in database
GPG key ID: C6E9A89936B8C143
7 changed files with 304 additions and 117 deletions

View file

@ -568,7 +568,6 @@ main := fn(): uint {
return big_array[42] return big_array[42]
} }
``` ```
note: this does not work on scalar values
#### generic_functions #### generic_functions
```hb ```hb
@ -678,6 +677,21 @@ main := fn(): uint {
} }
``` ```
#### unrolled_loops
```hb
Nums := struct {a: uint, b: u32, c: u16, d: u8}
main := fn(): uint {
nums := Nums.(1, 2, 3, 4)
i := 0
sum := 0
$loop if i == @len(Nums) break else {
sum += nums[i]
i += 1
}
return sum - 10
}
```
### Incomplete Examples ### Incomplete Examples

View file

@ -70,7 +70,7 @@ fn token_group(kind: TokenKind) -> TokenGroup {
| ShrAss | ShlAss => TG::Assign, | ShrAss | ShlAss => TG::Assign,
DQuote | Quote => TG::String, DQuote | Quote => TG::String,
Slf | Defer | Return | If | Else | Loop | Break | Continue | Fn | Idk | Die | Struct Slf | Defer | Return | If | Else | Loop | Break | Continue | Fn | Idk | Die | Struct
| Packed | True | False | Null | Match | Enum | Union => TG::Keyword, | Packed | True | False | Null | Match | Enum | Union | CtLoop => TG::Keyword,
} }
} }
@ -107,7 +107,7 @@ pub fn minify(source: &mut str) -> usize {
let mut token = lexer::Lexer::new(reader).eat(); let mut token = lexer::Lexer::new(reader).eat();
match token.kind { match token.kind {
TokenKind::Eof => break, TokenKind::Eof => break,
TokenKind::CtIdent | TokenKind::Directive => token.start -= 1, TokenKind::CtIdent | TokenKind::CtLoop | TokenKind::Directive => token.start -= 1,
_ => {} _ => {}
} }
@ -448,8 +448,8 @@ impl<'a> Formatter<'a> {
s.fmt(&br.body, f) s.fmt(&br.body, f)
}) })
} }
Expr::Loop { body, .. } => { Expr::Loop { body, unrolled, .. } => {
f.write_str("loop ")?; f.write_str(if unrolled { "$loop " } else { "loop " })?;
self.fmt(body, f) self.fmt(body, f)
} }
Expr::Closure { ret, body, args, .. } => { Expr::Closure { ret, body, args, .. } => {

View file

@ -32,6 +32,9 @@ macro_rules! gen_token_kind {
#[keywords] $( #[keywords] $(
$keyword:ident = $keyword_lit:literal, $keyword:ident = $keyword_lit:literal,
)* )*
#[const_keywords] $(
$const_keyword:ident = $const_keyword_lit:literal,
)*
#[punkt] $( #[punkt] $(
$punkt:ident = $punkt_lit:literal, $punkt:ident = $punkt_lit:literal,
)* )*
@ -56,6 +59,7 @@ macro_rules! gen_token_kind {
match *self { match *self {
$( Self::$pattern => concat!('<', stringify!($pattern), '>'), )* $( Self::$pattern => concat!('<', stringify!($pattern), '>'), )*
$( Self::$keyword => stringify!($keyword_lit), )* $( Self::$keyword => stringify!($keyword_lit), )*
$( Self::$const_keyword => concat!('$', $const_keyword_lit), )*
$( Self::$punkt => stringify!($punkt_lit), )* $( Self::$punkt => stringify!($punkt_lit), )*
$($( Self::$op => $op_lit, $($( Self::$op => $op_lit,
$(Self::$assign => concat!($op_lit, "="),)?)*)* $(Self::$assign => concat!($op_lit, "="),)?)*)*
@ -72,12 +76,23 @@ macro_rules! gen_token_kind {
} + 1) } + 1)
} }
#[allow(non_upper_case_globals)]
fn from_ident(ident: &[u8]) -> Self { fn from_ident(ident: &[u8]) -> Self {
$(const $keyword: &[u8] = $keyword_lit.as_bytes();)*
match ident { match ident {
$($keyword_lit => Self::$keyword,)* $($keyword => Self::$keyword,)*
_ => Self::Ident, _ => Self::Ident,
} }
} }
#[allow(non_upper_case_globals)]
fn from_ct_ident(ident: &[u8]) -> Self {
$(const $const_keyword: &[u8] = $const_keyword_lit.as_bytes();)*
match ident {
$($const_keyword => Self::$const_keyword,)*
_ => Self::CtIdent,
}
}
} }
}; };
} }
@ -156,6 +171,8 @@ pub enum TokenKind {
Die, Die,
Defer, Defer,
CtLoop,
// Unused = a-z // Unused = a-z
LBrace = b'{', LBrace = b'{',
Bor = b'|', Bor = b'|',
@ -305,26 +322,28 @@ gen_token_kind! {
Eof, Eof,
Directive, Directive,
#[keywords] #[keywords]
Slf = b"Self", Slf = "Self",
Return = b"return", Return = "return",
If = b"if", If = "if",
Match = b"match", Match = "match",
Else = b"else", Else = "else",
Loop = b"loop", Loop = "loop",
Break = b"break", Break = "break",
Continue = b"continue", Continue = "continue",
Fn = b"fn", Fn = "fn",
Struct = b"struct", Struct = "struct",
Packed = b"packed", Packed = "packed",
Enum = b"enum", Enum = "enum",
Union = b"union", Union = "union",
True = b"true", True = "true",
False = b"false", False = "false",
Null = b"null", Null = "null",
Idk = b"idk", Idk = "idk",
Die = b"die", Die = "die",
Defer = b"defer", Defer = "defer",
Under = b"_", Under = "_",
#[const_keywords]
CtLoop = "loop",
#[punkt] #[punkt]
Ctor = ".{", Ctor = ".{",
Tupl = ".(", Tupl = ".(",
@ -535,11 +554,17 @@ impl<'a> Lexer<'a> {
b'&' if self.advance_if(b'&') => T::And, b'&' if self.advance_if(b'&') => T::And,
b'|' if self.advance_if(b'|') => T::Or, b'|' if self.advance_if(b'|') => T::Or,
b'$' if self.advance_if(b':') => T::Ct, b'$' if self.advance_if(b':') => T::Ct,
b'@' | b'$' => { b'@' => {
start += 1; start += 1;
advance_ident(self); advance_ident(self);
identity(c) identity(c)
} }
b'$' => {
start += 1;
advance_ident(self);
let ident = &self.source[start as usize..self.pos as usize];
T::from_ct_ident(ident)
}
b'<' | b'>' if self.advance_if(c) => { b'<' | b'>' if self.advance_if(c) => {
identity(c - 5 + 128 * self.advance_if(b'=') as u8) identity(c - 5 + 128 * self.advance_if(b'=') as u8)
} }

View file

@ -447,7 +447,8 @@ impl<'a, 'b> Parser<'a, 'b> {
}) })
}, },
}, },
T::Loop => E::Loop { pos, body: self.ptr_expr()? }, T::Loop => E::Loop { pos, unrolled: false, body: self.ptr_expr()? },
T::CtLoop => E::Loop { pos, unrolled: true, body: self.ptr_expr()? },
T::Break => E::Break { pos }, T::Break => E::Break { pos },
T::Continue => E::Continue { pos }, T::Continue => E::Continue { pos },
T::Return => E::Return { T::Return => E::Return {
@ -984,6 +985,7 @@ generate_expr! {
/// `'loop' Expr` /// `'loop' Expr`
Loop { Loop {
pos: Pos, pos: Pos,
unrolled: bool,
body: &'a Self, body: &'a Self,
}, },
/// `('&' | '*' | '^') Expr` /// `('&' | '*' | '^') Expr`

View file

@ -2010,8 +2010,11 @@ impl Nodes {
debug_assert!(!var.ptr); debug_assert!(!var.ptr);
let [loops @ .., loob] = loops else { unreachable!() }; let [loops @ .., loob] = loops else { unreachable!() };
let node = loob.node; let &mut Loop::Runtime { node, ref mut scope, .. } = loob else {
let lvar = &mut loob.scope.vars[index]; self.load_loop_var(index, var, loops);
return;
};
let lvar = &mut scope.vars[index];
debug_assert!(!lvar.ptr); debug_assert!(!lvar.ptr);
@ -2033,8 +2036,11 @@ impl Nodes {
} }
let [loops @ .., loob] = loops else { unreachable!() }; let [loops @ .., loob] = loops else { unreachable!() };
let node = loob.node; let &mut Loop::Runtime { node, ref mut scope, .. } = loob else {
let lvar = &mut loob.scope.aclasses[index]; self.load_loop_aclass(index, aclass, loops);
return;
};
let lvar = &mut scope.aclasses[index];
self.load_loop_aclass(index, lvar, loops); self.load_loop_aclass(index, lvar, loops);
@ -2312,13 +2318,24 @@ type LoopDepth = u16;
type LockRc = u16; type LockRc = u16;
type IDomDepth = u16; type IDomDepth = u16;
#[derive(Clone, Copy)]
pub enum CtLoopState {
Terminated,
Continued,
}
#[derive(Clone)] #[derive(Clone)]
struct Loop { enum Loop {
node: Nid, Comptime {
ctrl: [StrongRef; 2], state: Option<(CtLoopState, Pos)>,
ctrl_scope: [Scope; 2], },
scope: Scope, Runtime {
defer_base: usize, node: Nid,
ctrl: [StrongRef; 2],
ctrl_scope: [Scope; 2],
scope: Scope,
defer_base: usize,
},
} }
mod strong_ref { mod strong_ref {
@ -2628,14 +2645,16 @@ impl Pool {
fn restore_ci(&mut self, dst: &mut ItemCtx) { fn restore_ci(&mut self, dst: &mut ItemCtx) {
self.used_cis -= 1; self.used_cis -= 1;
dst.scope.clear(&mut dst.nodes); dst.scope.clear(&mut dst.nodes);
dst.loops.drain(..).for_each(|mut l| { dst.loops.drain(..).for_each(|l| {
l.ctrl.map(|c| { if let Loop::Runtime { ctrl, ctrl_scope, mut scope, .. } = l {
if c.is_live() { ctrl.map(|c| {
c.remove(&mut dst.nodes); if c.is_live() {
} c.remove(&mut dst.nodes);
}); }
l.scope.clear(&mut dst.nodes); });
l.ctrl_scope.map(|mut s| s.clear(&mut dst.nodes)); scope.clear(&mut dst.nodes);
ctrl_scope.map(|mut s| s.clear(&mut dst.nodes));
}
}); });
mem::take(&mut dst.ctrl).remove(&mut dst.nodes); mem::take(&mut dst.ctrl).remove(&mut dst.nodes);
*dst = mem::take(&mut self.cis[self.used_cis]); *dst = mem::take(&mut self.cis[self.used_cis]);
@ -3399,38 +3418,67 @@ impl<'a> Codegen<'a> {
bs.ty = base; bs.ty = base;
} }
let ty::Kind::Slice(s) = bs.ty.expand() else { let mut idx = self.expr_ctx(index, Ctx::default().with_ty(ty::Id::DINT))?;
return self.error(
match bs.ty.expand() {
ty::Kind::Slice(s) => {
let elem = self.tys.ins.slices[s].elem;
let size = self.ci.nodes.new_const(ty::Id::INT, self.tys.size_of(elem));
self.assert_ty(index.pos(), &mut idx, ty::Id::DINT, "subscript");
let inps = [VOID, idx.id, size];
let offset = self.ci.nodes.new_node(
ty::Id::INT,
Kind::BinOp { op: TokenKind::Mul },
inps,
self.tys,
);
let aclass = self.ci.nodes.aclass_index(bs.id).1;
let inps = [VOID, bs.id, offset];
let ptr = self.ci.nodes.new_node(
ty::Id::INT,
Kind::BinOp { op: TokenKind::Add },
inps,
self.tys,
);
self.ci.nodes.pass_aclass(aclass, ptr);
Some(Value::ptr(ptr).ty(elem))
}
ty::Kind::Struct(s) => {
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(s, self.tys)
.into_iter(self.tys)
.nth(idx as _)
.map(|(f, off)| (f.ty, off))
else {
return self.error(
index.pos(),
fa!(
"struct '{}' has only `{}' fields, \
but index was '{}'",
self.ty_display(bs.ty),
self.tys.struct_fields(s).len(),
idx
),
);
};
Some(Value::ptr(self.offset(bs.id, offset)).ty(f))
}
_ => self.error(
base.pos(), base.pos(),
fa!( fa!(
"cant index into '{}' which is not array nor slice", "cant index into '{}' which is not array nor slice",
self.ty_display(bs.ty) self.ty_display(bs.ty)
), ),
); ),
}; }
let elem = self.tys.ins.slices[s].elem;
let mut idx = self.expr_ctx(index, Ctx::default().with_ty(ty::Id::DINT))?;
self.assert_ty(index.pos(), &mut idx, ty::Id::DINT, "subscript");
let size = self.ci.nodes.new_const(ty::Id::INT, self.tys.size_of(elem));
let inps = [VOID, idx.id, size];
let offset = self.ci.nodes.new_node(
ty::Id::INT,
Kind::BinOp { op: TokenKind::Mul },
inps,
self.tys,
);
let aclass = self.ci.nodes.aclass_index(bs.id).1;
let inps = [VOID, bs.id, offset];
let ptr = self.ci.nodes.new_node(
ty::Id::INT,
Kind::BinOp { op: TokenKind::Add },
inps,
self.tys,
);
self.ci.nodes.pass_aclass(aclass, ptr);
Some(Value::ptr(ptr).ty(elem))
} }
Expr::Embed { id, .. } => { Expr::Embed { id, .. } => {
let glob = &self.tys.ins.globals[id]; let glob = &self.tys.ins.globals[id];
@ -3447,6 +3495,20 @@ impl<'a> Codegen<'a> {
let align = self.tys.align_of(ty); let align = self.tys.align_of(ty);
self.gen_inferred_const(ctx, ty::Id::DINT, align, ty::Id::is_integer) self.gen_inferred_const(ctx, ty::Id::DINT, align, ty::Id::is_integer)
} }
Expr::Directive { name: "len", args: [ety], .. } => {
let ty = self.ty(ety);
let Some(len) = self.tys.len_of(ty) else {
return self.error(
ety.pos(),
fa!(
"'@len' only supports structs and arrays, \
'{}' is neither",
self.ty_display(ty)
),
);
};
self.gen_inferred_const(ctx, ty::Id::DINT, len, ty::Id::is_integer)
}
Expr::Directive { name: "bitcast", args: [val], pos } => { Expr::Directive { name: "bitcast", args: [val], pos } => {
let mut val = self.raw_expr(val)?; let mut val = self.raw_expr(val)?;
self.strip_var(&mut val); self.strip_var(&mut val);
@ -3886,7 +3948,47 @@ impl<'a> Codegen<'a> {
ret ret
} }
Expr::Loop { body, .. } => { Expr::Loop { unrolled: true, body, pos } => {
let mut loop_fuel = 100;
self.ci.loops.push(Loop::Comptime { state: None });
loop {
if loop_fuel == 0 {
return self.error(
pos,
"unrolled loop exceeded 100 iterations, use normal loop instead, TODO: add escape hatch",
);
}
loop_fuel -= 1;
let terminated = self.expr(body).is_none();
let Some(&Loop::Comptime { state }) = self.ci.loops.last() else {
unreachable!()
};
if !terminated && let Some((_, prev)) = state {
self.error(
pos,
"reached a constrol flow keyword inside an unrolled loop, \
as well ast the end of the loop, make sure control flow is \
not dependant on a runtime value",
);
return self.error(prev, "previous reachable control flow found here");
}
match state {
Some((CtLoopState::Terminated, _)) => break,
Some((CtLoopState::Continued, _)) | None => {}
}
}
self.ci.loops.pop().unwrap();
Some(Value::VOID)
}
Expr::Loop { body, unrolled: false, .. } => {
self.ci.ctrl.set( self.ci.ctrl.set(
self.ci.nodes.new_node( self.ci.nodes.new_node(
ty::Id::VOID, ty::Id::VOID,
@ -3897,7 +3999,7 @@ impl<'a> Codegen<'a> {
&mut self.ci.nodes, &mut self.ci.nodes,
); );
self.ci.loops.push(Loop { self.ci.loops.push(Loop::Runtime {
node: self.ci.ctrl.get(), node: self.ci.ctrl.get(),
ctrl: [StrongRef::DEFAULT; 2], ctrl: [StrongRef::DEFAULT; 2],
ctrl_scope: core::array::from_fn(|_| Default::default()), ctrl_scope: core::array::from_fn(|_| Default::default()),
@ -3920,8 +4022,11 @@ impl<'a> Codegen<'a> {
self.expr(body); self.expr(body);
let Loop { ctrl: [con, ..], ctrl_scope: [cons, ..], .. } = let Some(Loop::Runtime { ctrl: [con, ..], ctrl_scope: [cons, ..], .. }) =
self.ci.loops.last_mut().unwrap(); self.ci.loops.last_mut()
else {
unreachable!()
};
let mut cons = mem::take(cons); let mut cons = mem::take(cons);
if let Some(con) = mem::take(con).unwrap(&mut self.ci.nodes) { if let Some(con) = mem::take(con).unwrap(&mut self.ci.nodes) {
@ -3944,13 +4049,16 @@ impl<'a> Codegen<'a> {
cons.clear(&mut self.ci.nodes); cons.clear(&mut self.ci.nodes);
} }
let Loop { let Some(Loop::Runtime {
node, node,
ctrl: [.., bre], ctrl: [.., bre],
ctrl_scope: [.., mut bres], ctrl_scope: [.., mut bres],
mut scope, mut scope,
defer_base, defer_base,
} = self.ci.loops.pop().unwrap(); }) = self.ci.loops.pop()
else {
unreachable!()
};
self.gen_defers(defer_base); self.gen_defers(defer_base);
self.ci.defers.truncate(defer_base); self.ci.defers.truncate(defer_base);
@ -5006,52 +5114,75 @@ impl<'a> Codegen<'a> {
} }
fn jump_to(&mut self, pos: Pos, id: usize) -> Option<Value> { fn jump_to(&mut self, pos: Pos, id: usize) -> Option<Value> {
let Some(&mut Loop { defer_base, .. }) = self.ci.loops.last_mut() else { let Some(loob) = self.ci.loops.last_mut() else {
self.error(pos, "break outside a loop"); self.error(pos, "break outside a loop");
return None; return None;
}; };
self.gen_defers(defer_base)?; match loob {
&mut Loop::Comptime { state: Some((_, prev)) } => {
self.error(
pos,
"reached multiple control flow keywords inside an unrolled loop, \
make sure control flow is not dependant on a runtime value",
);
self.error(prev, "previous reachable control flow found here");
}
Loop::Comptime { state: state @ None } => {
*state = Some(([CtLoopState::Continued, CtLoopState::Terminated][id], pos))
}
&mut Loop::Runtime { defer_base, .. } => {
self.gen_defers(defer_base)?;
let mut loob = self.ci.loops.last_mut().unwrap(); let Loop::Runtime { ctrl: lctrl, ctrl_scope, scope, .. } =
self.ci.loops.last_mut().unwrap()
else {
unreachable!()
};
if loob.ctrl[id].is_live() { if lctrl[id].is_live() {
loob.ctrl[id].set( lctrl[id].set(
self.ci.nodes.new_node( self.ci.nodes.new_node(
ty::Id::VOID, ty::Id::VOID,
Kind::Region, Kind::Region,
[self.ci.ctrl.get(), loob.ctrl[id].get()], [self.ci.ctrl.get(), lctrl[id].get()],
self.tys, self.tys,
), ),
&mut self.ci.nodes, &mut self.ci.nodes,
); );
let mut scope = mem::take(&mut loob.ctrl_scope[id]); let mut scope = mem::take(&mut ctrl_scope[id]);
let ctrl = mem::take(&mut loob.ctrl[id]); let ctrl = mem::take(&mut lctrl[id]);
self.ci.nodes.merge_scopes( self.ci.nodes.merge_scopes(
&mut self.ci.loops, &mut self.ci.loops,
&ctrl, &ctrl,
&mut scope, &mut scope,
&mut self.ci.scope, &mut self.ci.scope,
self.tys, self.tys,
); );
loob = self.ci.loops.last_mut().unwrap(); let Loop::Runtime { ctrl: lctrl, ctrl_scope, .. } =
loob.ctrl_scope[id] = scope; self.ci.loops.last_mut().unwrap()
loob.ctrl[id] = ctrl; else {
self.ci.ctrl.set(NEVER, &mut self.ci.nodes); unreachable!()
} else { };
let term = StrongRef::new(NEVER, &mut self.ci.nodes); ctrl_scope[id] = scope;
loob.ctrl[id] = mem::replace(&mut self.ci.ctrl, term); lctrl[id] = ctrl;
loob.ctrl_scope[id] = self.ci.scope.dup(&mut self.ci.nodes); self.ci.ctrl.set(NEVER, &mut self.ci.nodes);
loob.ctrl_scope[id] } else {
.vars let term = StrongRef::new(NEVER, &mut self.ci.nodes);
.drain(loob.scope.vars.len()..) lctrl[id] = mem::replace(&mut self.ci.ctrl, term);
.for_each(|v| v.remove(&mut self.ci.nodes)); ctrl_scope[id] = self.ci.scope.dup(&mut self.ci.nodes);
loob.ctrl_scope[id] ctrl_scope[id]
.aclasses .vars
.drain(loob.scope.aclasses.len()..) .drain(scope.vars.len()..)
.for_each(|v| v.remove(&mut self.ci.nodes)); .for_each(|v| v.remove(&mut self.ci.nodes));
ctrl_scope[id]
.aclasses
.drain(scope.aclasses.len()..)
.for_each(|v| v.remove(&mut self.ci.nodes));
}
}
} }
None None
@ -6024,6 +6155,7 @@ mod tests {
generic_functions; generic_functions;
die; die;
defer; defer;
unrolled_loops;
// Incomplete Examples; // Incomplete Examples;
//comptime_pointers; //comptime_pointers;

View file

@ -1176,6 +1176,14 @@ impl Types {
debug_assert_eq!(captured.len(), base.captured.len()); debug_assert_eq!(captured.len(), base.captured.len());
Some((captured, base.captured)) Some((captured, base.captured))
} }
pub fn len_of(&self, ty: Id) -> Option<u32> {
Some(match ty.expand() {
Kind::Struct(s) => self.struct_field_range(s).len() as _,
Kind::Slice(s) => self.ins.slices[s].len()? as _,
_ => return None,
})
}
} }
pub struct OptLayout { pub struct OptLayout {

View file

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