From 8266fdfb00b53298777a529185bc9217a68058f3 Mon Sep 17 00:00:00 2001 From: Dylan Leifer-Ives Date: Mon, 8 Dec 2025 18:09:30 -0800 Subject: [PATCH] Implement some of the missing instructions Really implemented some of the larger systems that will be needed to create our larger system of instructions and the interface layers between them. Closes #5 --- ir.v | 117 +++++++++- ir_instruction_gen.v | 357 ++++++++++++++++++++++++++++--- lx.v | 20 +- main.v | 12 +- parse.v | 14 +- tests/ir/bb_gen/stmts/assign.mcf | 6 + tests/ir/bb_gen/stmts/define.mcf | 4 + utils_ir.v | 91 +++++++- 8 files changed, 574 insertions(+), 47 deletions(-) create mode 100644 tests/ir/bb_gen/stmts/assign.mcf create mode 100644 tests/ir/bb_gen/stmts/define.mcf diff --git a/ir.v b/ir.v index 62be94e..ff62efc 100644 --- a/ir.v +++ b/ir.v @@ -42,7 +42,7 @@ pub mut: refs []IRRef refs_map map[RID]VID vars_map map[string]VID - operand []IROperand + consts []IRConstant entrybb BBID is_inline bool // Later inline_defaults []int // Later @@ -74,6 +74,8 @@ pub mut: stmts []Stmt // The list of statements from the IR that form this basic block } +pub type BBAID = int + pub struct IRBasicBlockArg { pub mut: name string @@ -83,7 +85,12 @@ pub mut: } type IID = int -pub type IRInstruction = IRMacroLiteralCmd | IRDefine | IRTypedDefine | IRBinaryOp | IRUnaryOp +pub type IRInstruction = IRMacroLiteralCmd + | IRDefine + | IRTypedDefine + | IRBinaryOp + | IRUnaryOp + | IRAssign pub struct IRDefine { pub mut: @@ -98,14 +105,55 @@ pub mut: result RID } +pub fn (t TokenKind) to_assign_op() AssignOp { + return match t { + .assign { .assign } + .plus_assign { .add_assign } + .minus_assign { .sub_assign } + .star_assign { .mul_assign } + .slash_assign { .div_assign } + .percent_assign { .mod_assign } + .swap { .swap } + else { panic('illegal use of token ${t} as a Assign operation') } + } +} + pub enum AssignOp { assign - add - sub - mul - div - mod + add_assign + sub_assign + mul_assign + div_assign + mod_assign + swap +} + +pub fn (a AssignOp) print() { + match a { + .assign { + print('=') + } + .add_assign { + print('+=') + } + .sub_assign { + print('-=') + } + .mul_assign { + print('*=') + } + .div_assign { + print('/=') + } + .mod_assign { + print('%=') + } + .swap { + print('><') + } + } } + pub struct IRAssign { pub mut: id IID @@ -252,6 +300,7 @@ pub struct IRJump { pub mut: id IID target BBID + // TODO: add value here for calling the .mcfunction with args } pub struct IRBranch { @@ -260,6 +309,7 @@ pub mut: cond OID then BBID el BBID + // TODO: add values here for calling the .mcfunction with args } pub struct IRReturn { @@ -304,6 +354,7 @@ pub mut: text string } +// TODO: make it so that all string macros can be any expression just inside of a macro pub struct IRStringMacro { pub mut: value RID @@ -355,14 +406,50 @@ pub struct IRRef { pub mut: id RID value IRRefSum + typ IRType } // IR OPERAND -type OID = int -pub type IROperand = RID | IRConstant +type OID = RID | CID +@[inline] +pub fn (id OID) to_ir_type(f IRFunction) IRType { + match id { + RID { + return f.refs[id].typ + } + CID { + match f.consts[id] { + IRIntConst { + return BuiltinType.int_t + } + IRCharConst { + return BuiltinType.char_t + } + IRFloatConst { + return BuiltinType.float_t + } + IRStringConst { + return BuiltinType.string_t + } + IRListConst { + return BuiltinType.list_t + } + IRDictConst { + return BuiltinType.dict_t + } + else { + panic('Type not implemented in oid to ir type ${f.consts[id]}') + } + } + } + } +} + +type CID = int pub type IRConstant = IRIntConst | IRFloatConst + | IRCharConst | IRStringConst | IRListConst | IRDictConst @@ -373,6 +460,11 @@ pub mut: value int } +pub struct IRCharConst { +pub mut: + value string +} + pub struct IRFloatConst { pub mut: value f64 @@ -380,7 +472,6 @@ pub mut: pub struct IRStringConst { pub mut: - value string parts []IRStringPart // For interpolated strings } @@ -850,6 +941,9 @@ pub fn (mut b IRBuilder) literal_has_macro(l Literal) bool { } } } + FloatLiteral { + return false + } CharLiteral { return false } @@ -900,6 +994,9 @@ pub fn (mut b IRBuilder) literal_needs_block(l Literal) bool { IntegerLiteral { return false } + FloatLiteral { + return false + } StringLiteral { if !l.interpolated { return false diff --git a/ir_instruction_gen.v b/ir_instruction_gen.v index 3368679..28c1702 100644 --- a/ir_instruction_gen.v +++ b/ir_instruction_gen.v @@ -2,9 +2,8 @@ module main import datatypes -pub fn (mut b IRBuilder) bb_get_reference(name string, - bbid BBID, - fid FID) (bool, IRRef) { +// gets it and ads it to the local map +pub fn (mut b IRBuilder) bb_get_reference(name string, bbid BBID, fid FID) (bool, IRRef) { mut bb := b.basic_blocks[bbid] mut fun := b.functions[fid] mut ns := b.namespaces[fun.namespace] @@ -18,6 +17,7 @@ pub fn (mut b IRBuilder) bb_get_reference(name string, ref := IRRef{ id: fun.refs.len value: arg + typ: arg.typ } fun.refs << ref bb.vars_map[name] = ref.id @@ -77,20 +77,109 @@ pub fn (mut b IRBuilder) bb_get_reference(name string, return false, IRRef{} } +pub enum RefLevel { + invalid + bb_args + pred_block + pred_args + fun_args + namespace +} + +// Just looks for it does not add it anywhere +pub fn (mut b IRBuilder) bb_get_reference_no_creation(name string, bbid BBID, fid FID) (bool, IRRef, RefLevel) { + mut bb := b.basic_blocks[bbid] + mut fun := b.functions[fid] + mut ns := b.namespaces[fun.namespace] + + // check if its in our arguments + for arg in bb.args { + if arg.name == name { + ref := IRRef{ + id: RID(-1) + value: arg + typ: arg.typ + } + return true, ref, RefLevel.bb_args + } + } + + mut visited := map[BBID]bool{} + visited[bbid] = true + mut to_visit := datatypes.Queue[BBID]{} + for pred in bb.predecessors { + to_visit.push(pred) + } + + // check if its defined in any predecesor blocks + // who exist in the same scope TODO + for !to_visit.is_empty() { + new_id := to_visit.pop() or { panic("we really thought we'd have something lol") } + if new_id in visited { + continue + } + new := b.basic_blocks[new_id] + if name in new.vars_map { + ref := new.vars_map[name] + return true, fun.refs[ref], RefLevel.pred_block + } + + for arg in new.args { + if arg.name == name { + ref := IRRef{ + id: RID(-1) + value: arg + typ: arg.typ + } + return true, ref, RefLevel.pred_args + } + } + + visited[new_id] = true + for pred in new.predecessors { + to_visit.push(pred) + } + } + + // check if its in our function's arguments + for arg in fun.args { + if arg.name == name { + ref := IRRef{ + id: RID(-1) + value: arg + } + return true, ref, RefLevel.fun_args + } + } + + // check if its in our owning namespace + if name in ns.variable_map { + ref := IRRef{ + id: RID(-1) + value: IRRefSum(ns.variable_map[name]) + } + return true, ref, RefLevel.namespace + } + + return false, IRRef{}, RefLevel.invalid +} + pub fn (mut b IRBuilder) bb_add_reference(bbid BBID, vid VID) RID { ref := IRRef{ id: b.functions[b.basic_blocks[bbid].function].refs.len value: IRRefSum(vid) + typ: b.variables[vid].typ } b.functions[b.basic_blocks[bbid].function].refs << ref b.basic_blocks[bbid].vars_map[b.variables[vid].name] = ref.id return ref.id } -pub fn (mut b IRBuilder) bb_add_anon_reference(bbid BBID, iid IID) RID { +pub fn (mut b IRBuilder) bb_add_anon_reference(bbid BBID, iid IID, typ IRType) RID { ref := IRRef{ id: b.functions[b.basic_blocks[bbid].function].refs.len value: IRRefSum(iid) + typ: typ } b.functions[b.basic_blocks[bbid].function].refs << ref return ref.id @@ -100,8 +189,41 @@ pub fn (mut b IRBuilder) bb_add_anon_reference(bbid BBID, iid IID) RID { // bb := b.basic_blocks[bbid] // func := b.function[fid] // } +pub fn (mut b IRBuilder) bb_add_value_ir(bbid BBID, name string, typ IRType, source ValueType) VID { + bb := b.basic_blocks[bbid] + fid := bb.function + func := b.functions[fid] + ns := b.namespaces[func.namespace] + mut val := IRValue{ + name: name + id: b.variables.len + typ: typ + storage: source.to_ir() + location: match source { + .effemeral { + IRLocation(IREffLocation{ + value: name + }) + } + .data { + IRLocation(IRDataLocation{ + namespace: ns.id + function: fid + }) + } + .register { + IRLocation(IRRegLocation{ + namespace: ns.id + function: fid + }) + } + } + } + b.variables << val + return val.id +} -pub fn (mut b IRBuilder) bb_add_value(bbid BBID, name string, typ Type, source ValueType) VID { +pub fn (mut b IRBuilder) bb_add_value_ast(bbid BBID, name string, typ Type, source ValueType) VID { bb := b.basic_blocks[bbid] fid := bb.function func := b.functions[fid] @@ -137,6 +259,69 @@ pub fn (mut b IRBuilder) bb_add_value(bbid BBID, name string, typ Type, source V return val.id } +// returns if its a refered macro, and the RID for accessing it! +pub fn (mut b IRBuilder) bb_handle_macro_expr(bbid BBID, mexpr MacroExpr) (bool, RID) { + refed := mexpr.referable + fid := b.basic_blocks[bbid].function + // TODO: Implement going down the identifier chain -> will have to make macro expasions and such.... + // which will create more basic blocks. + chain := mexpr.ident_chain + if chain.elements.len > 1 { + panic('Not implemented identifier chains yet...') + } + e := chain.elements[0] + match e { + IdentifierChainName { + ok, ref := b.bb_get_reference(e.name.name, bbid, fid) + if !ok { + panic('could not find value to macro expand in ${mexpr}') + } + + match ref.value { + IRBasicBlockArg { + if ref.value.id == bbid { + // we don't have to add it as something that we want, someone has already done this for us. + return refed, ref.id + } + } + else {} + } + + // It is not an argument to ourself... We need to ask for this thing. + // As we are going to be using it as a macro value it has to be as a data -> we can set the storage type to data + // type IRRefSum = VID | IRBasicBlockArg | IRFunctionArg | IID + mut arg := IRBasicBlockArg{ + id: bbid + typ: ref.typ + storage: .data + } + match ref.value { + IRBasicBlockArg { + arg.name = ref.value.name + } + IRFunctionArg { + arg.name = ref.value.name + } + VID { + arg.name = b.variables[ref.value].name + } + IID { + panic('should be impossible for for an instruction to be the reference for a name...') + arg.name = '' + } + } + b.basic_blocks[bbid].args << arg + return refed, ref.id + } + else { + panic('not implemented anything other than identiferi chain name for macroexpr ${e}') + } + } + + panic('could not handle macro expr') + return refed, RID(-1) +} + // Returns an oid that we can set to something, later on we can merge the instructions together pub fn (mut b IRBuilder) lower_expression(bbid BBID, fid FID, expr Expr) OID { bb := b.basic_blocks[bbid] @@ -150,46 +335,118 @@ pub fn (mut b IRBuilder) lower_expression(bbid BBID, fid FID, expr Expr) OID { right: b.lower_expression(bbid, fid, expr.right) } inst.id = b.functions[fid].insts.len - inst.result = b.bb_add_anon_reference(bbid, inst.id) + inst.result = b.bb_add_anon_reference(bbid, inst.id, inst.left.to_ir_type(func)) b.functions[fid].insts << inst b.basic_blocks[bbid].insts << inst.id - b.functions[fid].operand << IROperand(inst.result) - return OID(b.functions[fid].operand.len - 1) + return OID(inst.result) } UnaryExpr { + // Will become not a literal, therefore we can have it not be a constant anymore mut inst := IRUnaryOp{ op: expr.operator.to_unary_op() operand: b.lower_expression(bbid, fid, expr.right) } inst.id = b.functions[fid].insts.len - inst.result = b.bb_add_anon_reference(bbid, inst.id) + inst.result = b.bb_add_anon_reference(bbid, inst.id, inst.operand.to_ir_type(func)) b.functions[fid].insts << inst b.basic_blocks[bbid].insts << inst.id - b.functions[fid].operand << IROperand(inst.result) - return OID(b.functions[fid].operand.len - 1) + return OID(inst.result) + } + Literal { + lit := expr + match lit { + IntegerLiteral { + cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << IRIntConst{ + value: lit.value + } + return OID(cid) + } + CharLiteral { + cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << IRCharConst{ + value: lit.value + } + return OID(cid) + } + FloatLiteral { + cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << IRFloatConst{ + value: lit.value + } + return OID(cid) + } + StringLiteral { + mut ir_str := IRStringConst{} + if lit.value != '' { + ir_str.parts << IRStringText{ + text: lit.value + } + } + if lit.interpolated { + for part in lit.parts { + if !part.is_macro { + ir_str.parts << IRStringText{ + text: part.text + } + continue + } + // We have a macro that we have to handle here... + refed, mrid := b.bb_handle_macro_expr(bbid, part.macro_expr or { + panic('should be a macro expression in this string literal???') + }) + mcro := IRStringMacro{ + value: mrid + is_ref: refed + } + ir_str.parts << mcro + } + } + cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << ir_str + return OID(cid) + } + ListLiteral { + panic('list literal not implemented') + } + DictionaryLiteral { + panic('dictionary literal not implemented') + } + RangeLiteral { + panic('range literal not implemented') + } + } } Identifier { // Will have to be a ref, and it must exist! otherwise we're boned boys ok, ref := b.bb_get_reference(expr.name, bbid, fid) if ok { - b.functions[fid].operand << IROperand(ref.id) - return OID(b.functions[fid].operand.len - 1) + return OID(ref.id) } else { panic('We could not find a reference to the identifier ${expr}') } } - Literal {} - MacroExpr {} - AccessExpr {} - StructLiteral {} - QualifiedIdentifier {} + MacroExpr { + panic('not implemented macro expr yet for lowering expressoin') + } + AccessExpr { + panic('not implemente d access expressoin yet for loweing expression') + } + StructLiteral { + panic('not implemented struct literal expr yet for loweing expressions') + } + QualifiedIdentifier { + panic('not yet implemented qualified identifier lowering for loweriing expresions yet') + } } - return OID(-1) + + panic('could not lower expression') + return OID(CID(-1)) } pub fn (mut b IRBuilder) fill_bb_insts_stmt(bbid BBID, fid FID, stmt Stmt) { - bb := b.basic_blocks[bbid] - func := b.functions[fid] - ns := b.namespaces[func.namespace] + mut bb := b.basic_blocks[bbid] + mut func := b.functions[fid] + mut ns := b.namespaces[func.namespace] match stmt { TypedDefine { ok, ref := b.bb_get_reference(stmt.name.name, bbid, fid) @@ -197,17 +454,11 @@ pub fn (mut b IRBuilder) fill_bb_insts_stmt(bbid BBID, fid FID, stmt Stmt) { // This should not be allowed.... We are not allowed to have // this sort of shadowing! panic('When defining ${stmt} we found a reference to it already existing.') - // inst := IRTypedDefine{ - // id: func.insts.len - // result: ref.id - // } - // b.functions[fid].insts << inst - // b.basic_blocks[bbid].insts << inst.id } else { // No reference to this exists so we should like make one // need to create a value inst := IRTypedDefine{ id: func.insts.len - result: b.bb_add_reference(bbid, b.bb_add_value(bbid, stmt.name.name, + result: b.bb_add_reference(bbid, b.bb_add_value_ast(bbid, stmt.name.name, stmt.typ, stmt.source)) } b.functions[fid].insts << inst @@ -222,7 +473,55 @@ pub fn (mut b IRBuilder) fill_bb_insts_stmt(bbid BBID, fid FID, stmt Stmt) { } else { // We are the first creation point for this thing! // We have to first figure out the type of the expression that we are coming from... // And generate the instructions that compose this expression... + mut inst := IRDefine{ + value: b.lower_expression(bbid, fid, stmt.value) + } + inst.result = b.bb_add_reference(bbid, b.bb_add_value_ir(bbid, stmt.name.name, + inst.value.to_ir_type(b.functions[fid]), stmt.source)) + inst.id = b.functions[fid].insts.len + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + } + } + Assignment { + op := stmt.operator.to_assign_op() + left_oid := b.lower_expression(bbid, fid, stmt.left) + left := match left_oid { + CID { + panic('Cannot assign to a constant value') + RID(-1) + } + RID { + left_oid + } } + + right := b.lower_expression(bbid, fid, stmt.right) + match op { + .assign { + // Check that left and right are same type. + if OID(left).to_ir_type(b.functions[fid]) != right.to_ir_type(b.functions[fid]) { + panic('cannot assign accross types') + } + // TODO: handle checking if the sources are different to + } + else { + lref := b.functions[fid].refs[left] + if lref.typ is BuiltinType { + if lref.typ != BuiltinType.int_t { + panic('cannot do operation ${op} onto type ${lref.typ}') + } + } + } + } + mut inst := IRAssign{ + id: b.functions[fid].insts.len + result: left + op: op + value: right + } + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id } else { println('not implemented ${stmt}') diff --git a/lx.v b/lx.v index 12797bb..c98096a 100644 --- a/lx.v +++ b/lx.v @@ -6,6 +6,7 @@ enum TokenKind { invalid lit_string lit_integer + lit_float lit_char eof ident @@ -154,6 +155,23 @@ pub fn (mut l Lexer) read_integer() Token { } } +pub fn (mut l Lexer) read_integer_or_float() Token { + start := l.index + first := l.read_integer() + if (l.peek() == `.`) { + l.index += 1 + second := l.read_integer() + + str := l.src[start..l.index] + return Token{ + kind: .lit_float + str: str + pos: start + } + } + return first +} + pub fn (mut l Lexer) read_string() Token { start := l.index mut counter := 0 @@ -491,7 +509,7 @@ pub fn (mut l Lexer) next_token() Token { return l.read_identifier() } is_digit(p) { - return l.read_integer() + return l.read_integer_or_float() } else { start := l.index diff --git a/main.v b/main.v index 5984c07..8699681 100644 --- a/main.v +++ b/main.v @@ -2,11 +2,19 @@ module main fn main() { mut irb := IRBuilder{} - irb.lower(['tests/ir/bb_gen/stmts/typed_define.mcf'])! + irb.lower(['tests/ir/bb_gen/stmts/assign.mcf'])! irb.print_cfg_dot() - for bb in irb.basic_blocks { + for mut bb in irb.basic_blocks { irb.fill_bb_insts(bb.id) print(irb.functions[bb.function].insts) + print('\n') + print(bb.insts) + print('\n') + for inst in bb.insts { + irb.fn_print_inst(bb.function, inst) + print('\n') + } + // print(irb.functions[bb.function].block) } // for func in irb.functions { diff --git a/parse.v b/parse.v index eb8381a..73adeb8 100644 --- a/parse.v +++ b/parse.v @@ -61,6 +61,7 @@ pub type Literal = IntegerLiteral | ListLiteral | DictionaryLiteral | RangeLiteral + | FloatLiteral pub type AccessExpr = MemberAccessExpr | IndexAccessExpr | FunctionCallExpr @@ -277,6 +278,12 @@ mut: value string } +pub struct FloatLiteral { + Node +mut: + value f32 +} + pub struct ListLiteral { Node mut: @@ -318,6 +325,7 @@ mut: name string } +// TODO: make macros that can belong in strings handle any expression pub struct MacroExpr { Node mut: @@ -426,8 +434,9 @@ pub type Type = BuiltinType | ReferenceType | StructType pub enum BuiltinType { int_t float_t - list_t + char_t string_t + list_t dict_t void_t } @@ -1793,8 +1802,9 @@ fn (mut p Parser) parse_statement() Stmt { match p.current.kind { .assign, .plus_assign, .minus_assign, .star_assign, .slash_assign, .percent_assign, - .eq, .ne, .lte, .gte, .lcarrot, .rcarrot, .swap, .plus, .minus, .star, .slash, + .swap, .eq, .ne, .lte, .gte, .lcarrot, .rcarrot, .plus, .minus, .star, .slash, .percent { + // TODO: check if we rip this out will it still work well.... because like ermmm no? op := p.current.kind p.advance() right := p.parse_expr(0) diff --git a/tests/ir/bb_gen/stmts/assign.mcf b/tests/ir/bb_gen/stmts/assign.mcf new file mode 100644 index 0000000..aecc771 --- /dev/null +++ b/tests/ir/bb_gen/stmts/assign.mcf @@ -0,0 +1,6 @@ +fn a() : Void{ + data b : Int; + data c := 10; + b = 10; + c = c + b; +} diff --git a/tests/ir/bb_gen/stmts/define.mcf b/tests/ir/bb_gen/stmts/define.mcf new file mode 100644 index 0000000..4714f1d --- /dev/null +++ b/tests/ir/bb_gen/stmts/define.mcf @@ -0,0 +1,4 @@ +fn a() : Void{ + data b : Int; + data c := b; +} diff --git a/utils_ir.v b/utils_ir.v index 2da5a19..10fd013 100644 --- a/utils_ir.v +++ b/utils_ir.v @@ -124,10 +124,95 @@ pub fn (mut b IRBuilder) print_ir_location(location IRLocation) { pub fn (mut b IRBuilder) fn_print_rid(fid FID, rid RID) { func := b.functions[fid] ref := func.refs[rid] - match ref.value { - VID {} + print('(${ref.typ} ') + v := ref.value + match v { + VID { + b.print_ir_location(b.variables[v].location) + print(').${b.variables[v].name} ') + } IRBasicBlockArg {} IRFunctionArg {} - IID {} + IID { + print(b.namespaces[func.namespace].name) + print(':') + print(func.name) + print(':inst_${v}') + } + } +} + +pub fn (mut b IRBuilder) fn_print_oid(fid FID, id OID) { + func := b.functions[fid] + match id { + CID { + c := b.functions[fid].consts[id] + match c { + IRFloatConst { + print('${c.value}') + } + IRIntConst { + print('${c.value}') + } + IRCharConst { + print('${c.value}') + } + IRStringConst { + for part in c.parts { + match part { + IRStringText { + print(part.text) + } + IRStringMacro { + if part.is_ref { + print('&') + } + b.fn_print_rid(fid, part.value) + } + } + } + } + else { + print('const ${c} not yet supported') + } + } + } + RID { + b.fn_print_rid(fid, id) + } + } +} + +pub fn (mut b IRBuilder) fn_print_inst(fid FID, iid IID) { + func := b.functions[fid] + inst := func.insts[iid] + match inst { + IRTypedDefine { + print('define ') + b.fn_print_rid(fid, inst.result) + } + IRDefine { + print('define ') + b.fn_print_rid(fid, inst.result) + print(' = ') + b.fn_print_oid(fid, inst.value) + } + IRAssign { + print('assign ') + b.fn_print_rid(fid, inst.result) + inst.op.print() + b.fn_print_oid(fid, inst.value) + } + IRBinaryOp { + print('binop ') + b.fn_print_rid(fid, inst.result) + print(' = ') + b.fn_print_oid(fid, inst.left) + print(inst.op) + b.fn_print_oid(fid, inst.right) + } + else { + print('inst is not supported yes ${inst}') + } } }