mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
IR lowering: constant expression folding
Constants can now reference other constants and use arithmetic /
bitwise / comparison operators. The lowering runs \`eval_const\` on
each \`ConstDecl\` and \`VarDecl\` initializer, threading the existing
\`const_values\` map for identifier lookups.
Examples that now work:
const BASE: u8 = 10
const OFFSET: u8 = BASE + 5 // 15
const MASK: u8 = FLAG_A | FLAG_B // bitwise-or of two earlier consts
var counter: u8 = BASE // initializer is now 10
Enum variants are registered before constants so a const like
\`const FIRST: u8 = VariantA\` also resolves at compile time.
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
0a3e830acf
commit
8f0e0635df
2 changed files with 96 additions and 16 deletions
|
|
@ -87,6 +87,53 @@ impl LoweringContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to evaluate an expression at compile time, using the
|
||||||
|
/// already-registered constants as operands. Returns `None` if
|
||||||
|
/// the expression references something that isn't known at this
|
||||||
|
/// point (e.g. a runtime variable) or contains an operator we
|
||||||
|
/// don't constant-fold. The result is a u16 to keep the same
|
||||||
|
/// range as the AST integer literal type.
|
||||||
|
fn eval_const(&self, expr: &Expr) -> Option<u16> {
|
||||||
|
match expr {
|
||||||
|
Expr::IntLiteral(v, _) => Some(*v),
|
||||||
|
Expr::BoolLiteral(b, _) => Some(u16::from(*b)),
|
||||||
|
Expr::Ident(name, _) => self.const_values.get(name).copied(),
|
||||||
|
Expr::BinaryOp(lhs, op, rhs, _) => {
|
||||||
|
let l = self.eval_const(lhs)?;
|
||||||
|
let r = self.eval_const(rhs)?;
|
||||||
|
match op {
|
||||||
|
BinOp::Add => Some(l.wrapping_add(r)),
|
||||||
|
BinOp::Sub => Some(l.wrapping_sub(r)),
|
||||||
|
BinOp::Mul => Some(l.wrapping_mul(r)),
|
||||||
|
BinOp::Div if r != 0 => Some(l / r),
|
||||||
|
BinOp::Mod if r != 0 => Some(l % r),
|
||||||
|
BinOp::BitwiseAnd => Some(l & r),
|
||||||
|
BinOp::BitwiseOr => Some(l | r),
|
||||||
|
BinOp::BitwiseXor => Some(l ^ r),
|
||||||
|
BinOp::ShiftLeft => Some(l.wrapping_shl(u32::from(r))),
|
||||||
|
BinOp::ShiftRight => Some(l.wrapping_shr(u32::from(r))),
|
||||||
|
BinOp::Eq => Some(u16::from(l == r)),
|
||||||
|
BinOp::NotEq => Some(u16::from(l != r)),
|
||||||
|
BinOp::Lt => Some(u16::from(l < r)),
|
||||||
|
BinOp::Gt => Some(u16::from(l > r)),
|
||||||
|
BinOp::LtEq => Some(u16::from(l <= r)),
|
||||||
|
BinOp::GtEq => Some(u16::from(l >= r)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::UnaryOp(op, inner, _) => {
|
||||||
|
let v = self.eval_const(inner)?;
|
||||||
|
match op {
|
||||||
|
UnaryOp::Negate => Some(v.wrapping_neg()),
|
||||||
|
UnaryOp::BitNot => Some(!v),
|
||||||
|
UnaryOp::Not => Some(u16::from(v == 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Cast(inner, _, _) => self.eval_const(inner),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn emit(&mut self, op: IrOp) {
|
fn emit(&mut self, op: IrOp) {
|
||||||
self.current_ops.push(op);
|
self.current_ops.push(op);
|
||||||
}
|
}
|
||||||
|
|
@ -119,30 +166,26 @@ impl LoweringContext {
|
||||||
self.state_names = program.states.iter().map(|s| s.name.clone()).collect();
|
self.state_names = program.states.iter().map(|s| s.name.clone()).collect();
|
||||||
program.start_state.clone_into(&mut self.start_state);
|
program.start_state.clone_into(&mut self.start_state);
|
||||||
|
|
||||||
// Register constants
|
// Register enum variants first so constants that reference
|
||||||
for c in &program.constants {
|
// them (e.g. `const FIRST: u8 = VariantA`) can resolve.
|
||||||
if let Expr::IntLiteral(v, _) = &c.value {
|
|
||||||
self.const_values.insert(c.name.clone(), *v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register enum variants as constants (0, 1, 2, ... per enum)
|
|
||||||
for e in &program.enums {
|
for e in &program.enums {
|
||||||
for (i, (variant, _)) in e.variants.iter().enumerate() {
|
for (i, (variant, _)) in e.variants.iter().enumerate() {
|
||||||
self.const_values.insert(variant.clone(), i as u16);
|
self.const_values.insert(variant.clone(), i as u16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lower globals
|
// Register constants with constant-evaluation. Each const
|
||||||
|
// may reference earlier constants.
|
||||||
|
for c in &program.constants {
|
||||||
|
if let Some(v) = self.eval_const(&c.value) {
|
||||||
|
self.const_values.insert(c.name.clone(), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower globals. Initializers can be any constant expression.
|
||||||
for var in &program.globals {
|
for var in &program.globals {
|
||||||
let var_id = self.get_or_create_var(&var.name);
|
let var_id = self.get_or_create_var(&var.name);
|
||||||
let init = var.init.as_ref().and_then(|e| {
|
let init = var.init.as_ref().and_then(|e| self.eval_const(e));
|
||||||
if let Expr::IntLiteral(v, _) = e {
|
|
||||||
Some(*v)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.globals.push(IrGlobal {
|
self.globals.push(IrGlobal {
|
||||||
var_id,
|
var_id,
|
||||||
name: var.name.clone(),
|
name: var.name.clone(),
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,43 @@ fn lower_constants_become_immediates() {
|
||||||
assert!(has_imm3, "constant should be inlined as LoadImm");
|
assert!(has_imm3, "constant should be inlined as LoadImm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lower_const_expressions_constant_fold() {
|
||||||
|
// Constants may reference earlier constants and use arithmetic.
|
||||||
|
// `B` resolves to `A + 3` = 8 at lowering time.
|
||||||
|
let ir = lower_ok(
|
||||||
|
r#"
|
||||||
|
game "Test" { mapper: NROM }
|
||||||
|
const A: u8 = 5
|
||||||
|
const B: u8 = A + 3
|
||||||
|
var x: u8 = B
|
||||||
|
on frame { wait_frame }
|
||||||
|
start Main
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let x_global = ir.globals.iter().find(|g| g.name == "x").unwrap();
|
||||||
|
assert_eq!(x_global.init_value, Some(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lower_const_bit_ops() {
|
||||||
|
// Bitwise constant folding should work for things like defining
|
||||||
|
// flags or masks based on other constants.
|
||||||
|
let ir = lower_ok(
|
||||||
|
r#"
|
||||||
|
game "Test" { mapper: NROM }
|
||||||
|
const FLAG_A: u8 = 1
|
||||||
|
const FLAG_B: u8 = 2
|
||||||
|
const BOTH: u8 = FLAG_A | FLAG_B
|
||||||
|
var x: u8 = BOTH
|
||||||
|
on frame { wait_frame }
|
||||||
|
start Main
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let x_global = ir.globals.iter().find(|g| g.name == "x").unwrap();
|
||||||
|
assert_eq!(x_global.init_value, Some(3));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lower_multiple_states() {
|
fn lower_multiple_states() {
|
||||||
let ir = lower_ok(
|
let ir = lower_ok(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue