diff --git a/src/Avr8Sharp/Core/Utils/Assembler.cs b/src/Avr8Sharp/Core/Utils/Assembler.cs index 99b15b5..9796c37 100644 --- a/src/Avr8Sharp/Core/Utils/Assembler.cs +++ b/src/Avr8Sharp/Core/Utils/Assembler.cs @@ -34,7 +34,9 @@ public partial class AvrAssembler var r = 0x2000 | DestRIndex(a) | SrcRIndex(b); return ZeroPad (r); } }, - { "ANDI", (a, b, _, _) => { + { "ANDI", (a, b, byteLoc, labels) => { + if (ConstOrLabel(b, labels) == int.MinValue) + return new Func, string> ((l) => OpTable?["ANDI"](a, b, byteLoc, l) as string ?? string.Empty); var r = 0x7000 | (DestRIndex(a, 16, 31) & 0xf0); var k = ConstValue(b); r |= ((k & 0xf0) << 4) | (k & 0xf); @@ -198,7 +200,9 @@ public partial class AvrAssembler var r = 0x0400 | DestRIndex(a) | SrcRIndex(b); return ZeroPad (r); }}, - { "CPI", (a, b, _, _) => { + { "CPI", (a, b, byteLoc, labels) => { + if (ConstOrLabel(b, labels) == int.MinValue) + return new Func, string> ((l) => OpTable?["CPI"](a, b, byteLoc, l) as string ?? string.Empty); var r = 0x3000 | (DestRIndex(a, 16, 31) & 0xf0); var k = ConstValue(b); r |= ((k & 0xf0) << 4) | (k & 0xf); @@ -312,7 +316,9 @@ public partial class AvrAssembler var r = DestRIndex(a) | StldYzQ(b); return ZeroPad (r); }}, - { "LDI", (a, b, _, _) => { + { "LDI", (a, b, byteLoc, labels) => { + if (ConstOrLabel(b, labels) == int.MinValue) + return new Func, string> ((l) => OpTable?["LDI"](a, b, byteLoc, l) as string ?? string.Empty); var r = 0xe000 | (DestRIndex(a, 16, 31) & 0xf0); var k = ConstValue(b); r |= ((k & 0xf0) << 4) | (k & 0xf); @@ -382,7 +388,9 @@ public partial class AvrAssembler var r = 0x2800 | DestRIndex(a) | SrcRIndex(b); return ZeroPad (r); }}, - { "ORI", (a, b, _, _) => { + { "ORI", (a, b, byteLoc, labels) => { + if (ConstOrLabel(b, labels) == int.MinValue) + return new Func, string> ((l) => OpTable?["ORI"](a, b, byteLoc, l) as string ?? string.Empty); var r = 0x6000 | (DestRIndex(a, 16, 31) & 0xf0); var k = ConstValue(b); r |= ((k & 0xf0) << 4) | (k & 0xf); @@ -435,7 +443,9 @@ public partial class AvrAssembler var r = 0x0800 | DestRIndex(a) | SrcRIndex(b); return ZeroPad (r); }}, - { "SBCI", (a, b, _, _) => { + { "SBCI", (a, b, byteLoc, labels) => { + if (ConstOrLabel(b, labels) == int.MinValue) + return new Func, string> ((l) => OpTable?["SBCI"](a, b, byteLoc, l) as string ?? string.Empty); var r = 0x4000 | (DestRIndex(a, 16, 31) & 0xf0); var k = ConstValue(b); r |= ((k & 0xf0) << 4) | (k & 0xf); @@ -540,7 +550,9 @@ public partial class AvrAssembler var r = 0x1800 | DestRIndex(a) | SrcRIndex(b); return ZeroPad (r); }}, - { "SUBI", (a, b, _, _) => { + { "SUBI", (a, b, byteLoc, labels) => { + if (ConstOrLabel(b, labels) == int.MinValue) + return new Func, string> ((l) => OpTable?["SUBI"](a, b, byteLoc, l) as string ?? string.Empty); var r = 0x5000 | (DestRIndex(a, 16, 31) & 0xf0); var k = ConstValue(b); r |= ((k & 0xf0) << 4) | (k & 0xf); @@ -1132,7 +1144,9 @@ private void PassOne (string inputData) } if (!OpTable.ContainsKey (instruction)) { - throw new Exception("Invalid instruction"); + if (instruction == "__GCC_ISR") + throw new Exception("__gcc_isr is an avr-as ISR-prologue pseudo-op; recompile with -mno-gas-isr-prologues to get explicit push/pop prologues"); + throw new Exception($"Invalid instruction: {instruction}"); } // Apply replacements and symbol substitution on parameters @@ -1241,7 +1255,8 @@ private void ProcessConditional(string directive, string args, int byteOffset, r // ----------------------------------------------------------------------- private void ProcessSymbolDef(string args, int lineIdx, int byteOffset, bool isImmutable) { - var parts = args.Split('=', 2); + // Accept both Atmel `NAME = VALUE` and GNU `NAME, VALUE` forms. + var parts = args.Contains('=') ? args.Split('=', 2) : args.Split(new[] { ',' }, 2); if (parts.Length != 2) { _errors.Add($"Line {lineIdx + 1}: .equ/.set requires NAME = VALUE"); return; } var name = parts[0].Trim(); var valStr = parts[1].Trim(); @@ -1290,68 +1305,74 @@ private int DataReserveSize(string dirName, string args) // Data-emission helpers // ----------------------------------------------------------------------- private void EmitDataBytes(string args, int lineIdx, ref int byteOffset, string rawLine) + => EmitData(args, lineIdx, ref byteOffset, rawLine, unit: 1); + + private void EmitDataWords(string args, int lineIdx, ref int byteOffset, string rawLine) + => EmitData(args, lineIdx, ref byteOffset, rawLine, unit: 2); + + private void EmitDataDwords(string args, int lineIdx, ref int byteOffset, string rawLine) + => EmitData(args, lineIdx, ref byteOffset, rawLine, unit: 4); + + // Emit .byte/.word/.dword data. The byte count is always known up front, so + // byteOffset advances deterministically; if any element is a forward-referenced + // symbol expression, the actual bytes are produced by a deferred closure that + // re-evaluates in pass two (mirrors how instructions defer label resolution). + private void EmitData(string args, int lineIdx, ref int byteOffset, string rawLine, int unit) { var parts = SplitDirectiveArgs(args); - var bytes = new List(); + + // Byte count and whether any element needs deferral. + int count = 0; + bool deferred = false; foreach (var part in parts) { var p = part.Trim(); - if (p.StartsWith('"') || p.StartsWith('\'')) + if (unit == 1 && (p.StartsWith('"') || p.StartsWith('\''))) { - var s = p.Trim('"', '\''); - bytes.AddRange(System.Text.Encoding.ASCII.GetBytes(s)); + count += p.Trim('"', '\'').Length; continue; } - var val = ExpressionEvaluator.TryEvaluate(p, _symbolTable, byteOffset); - if (val == null) { _errors.Add($"Line {lineIdx + 1}: Cannot evaluate .byte expression: {p}"); return; } - bytes.Add((byte)(val.Value & 0xFF)); - } - if (bytes.Count > 0) - { - var lt = new LineTablePassOne { Text = rawLine.Trim(), Line = lineIdx + 1, BytesOffset = byteOffset, Bytes = bytes.ToArray() }; - _lines.Add(lt); - byteOffset += bytes.Count; + count += unit; + if (ExpressionEvaluator.TryEvaluate(p, _symbolTable, byteOffset) == null) + deferred = true; } - } + if (count == 0) return; - private void EmitDataWords(string args, int lineIdx, ref int byteOffset, string rawLine) - { - var parts = SplitDirectiveArgs(args); - var wordBytes = new List(); - foreach (var part in parts) + int pc = byteOffset; + object bytes; + if (deferred) { - var val = ExpressionEvaluator.TryEvaluate(part.Trim(), _symbolTable, byteOffset); - if (val == null) { _errors.Add($"Line {lineIdx + 1}: Cannot evaluate .word expression: {part.Trim()}"); return; } - wordBytes.Add((byte)(val.Value & 0xFF)); - wordBytes.Add((byte)((val.Value >> 8) & 0xFF)); + bytes = new Func(_ => BuildDataBytes(parts, unit, pc)); } - if (wordBytes.Count > 0) + else { - var lt = new LineTablePassOne { Text = rawLine.Trim(), Line = lineIdx + 1, BytesOffset = byteOffset, Bytes = wordBytes.ToArray() }; - _lines.Add(lt); - byteOffset += wordBytes.Count; + try { bytes = BuildDataBytes(parts, unit, pc); } + catch (Exception ex) { _errors.Add($"Line {lineIdx + 1}: {ex.Message}"); return; } } + + _lines.Add(new LineTablePassOne { Text = rawLine.Trim(), Line = lineIdx + 1, BytesOffset = byteOffset, Bytes = bytes }); + byteOffset += count; } - private void EmitDataDwords(string args, int lineIdx, ref int byteOffset, string rawLine) + // Build the raw bytes for a .byte/.word/.dword directive. Throws if a symbol is + // still unresolved (caught by the caller / pass-two loop and reported as an error). + private byte[] BuildDataBytes(List parts, int unit, int pc) { - var parts = SplitDirectiveArgs(args); - var dwordBytes = new List(); + var bytes = new List(); foreach (var part in parts) { - var val = ExpressionEvaluator.TryEvaluate(part.Trim(), _symbolTable, byteOffset); - if (val == null) { _errors.Add($"Line {lineIdx + 1}: Cannot evaluate .dword expression: {part.Trim()}"); return; } - dwordBytes.Add((byte)(val.Value & 0xFF)); - dwordBytes.Add((byte)((val.Value >> 8) & 0xFF)); - dwordBytes.Add((byte)((val.Value >> 16) & 0xFF)); - dwordBytes.Add((byte)((val.Value >> 24) & 0xFF)); - } - if (dwordBytes.Count > 0) - { - var lt = new LineTablePassOne { Text = rawLine.Trim(), Line = lineIdx + 1, BytesOffset = byteOffset, Bytes = dwordBytes.ToArray() }; - _lines.Add(lt); - byteOffset += dwordBytes.Count; + var p = part.Trim(); + if (unit == 1 && (p.StartsWith('"') || p.StartsWith('\''))) + { + bytes.AddRange(System.Text.Encoding.ASCII.GetBytes(p.Trim('"', '\''))); + continue; + } + var val = ExpressionEvaluator.TryEvaluate(p, _currentSymbolTable ?? _symbolTable, pc) + ?? throw new Exception($"Cannot evaluate data expression: {p}"); + for (int i = 0; i < unit; i++) + bytes.Add((byte)((val >> (8 * i)) & 0xFF)); } + return bytes.ToArray(); } private void EmitDataAscii(string args, int lineIdx, ref int byteOffset, string rawLine, bool nullTerminated) diff --git a/src/Avr8Sharp/Core/Utils/ExpressionEvaluator.cs b/src/Avr8Sharp/Core/Utils/ExpressionEvaluator.cs index 6ee3e02..3141510 100644 --- a/src/Avr8Sharp/Core/Utils/ExpressionEvaluator.cs +++ b/src/Avr8Sharp/Core/Utils/ExpressionEvaluator.cs @@ -184,6 +184,10 @@ public static int Evaluate(string expr, SymbolTable symbols, int currentPc = 0) "LWRD" => arg.Value & 0xFFFF, "HWRD" => (arg.Value >> 16) & 0xFFFF, "PM" => arg.Value >> 1, + // gs() yields the word address of a symbol (for function pointers / + // far jumps). On <=128KW parts this equals pm(); the >128K stub is + // not needed for the simulated devices. + "GS" => arg.Value >> 1, _ => throw new Exception($"Unknown function: {name}") }; } diff --git a/tests/Avr8Sharp.Tests/AssemblerTests.cs b/tests/Avr8Sharp.Tests/AssemblerTests.cs index e69ba36..acb7498 100644 --- a/tests/Avr8Sharp.Tests/AssemblerTests.cs +++ b/tests/Avr8Sharp.Tests/AssemblerTests.cs @@ -920,6 +920,69 @@ public void Deferred4ByteAsLastLine () Assert.That (Bytes ("00910001"), Is.EqualTo (lds.Assemble ("lds r16, x\n.data\nx: .byte 1"))); } + [Test(Description = "Immediate instructions defer forward-referenced symbol operands (avr-gcc ldi lo8(sym))")] + public void Immediate_ForwardSymbol () + { + // buf resolves to 0x100: lo8 = 0x00, hi8 = 0x01 + var lo = new AvrAssembler (); + Assert.That (Bytes ("80e0"), Is.EqualTo (lo.Assemble ("ldi r24, lo8(buf)\n.data\nbuf: .byte 1"))); + var hi = new AvrAssembler (); + Assert.That (Bytes ("91e0"), Is.EqualTo (hi.Assemble ("ldi r25, hi8(buf)\n.data\nbuf: .byte 1"))); + // subi/sbci/ori/andi/cpi take the same path + var subi = new AvrAssembler (); + subi.Assemble ("subi r24, lo8(-(buf))\n.data\nbuf: .byte 1"); + Assert.That (subi.Errors, Is.Empty); + } + + [Test(Description = "gs() yields the word address of a symbol (function pointers), matching avr-as")] + public void GsFunction () + { + var assembler = new AvrAssembler (); + // f at byte 8 (word 4): lo8(gs(f)) = 0x04, hi8(gs(f)) = 0x00 + var result = assembler.Assemble ("ldi r24, lo8(gs(f))\nldi r25, hi8(gs(f))\nnop\nnop\nf: ret"); + + Assert.That (assembler.Errors, Is.Empty); + Assert.That (Bytes ("84e0" + "90e0" + "0000" + "0000" + "0895"), Is.EqualTo (result)); + } + + [Test(Description = ".set / .equ accept the GNU comma form (NAME, VALUE)")] + public void SymbolDef_CommaForm () + { + var s = new AvrAssembler (); + Assert.That (Bytes ("0ae2"), Is.EqualTo (s.Assemble (".set foo, 0x2a\nldi r16, foo"))); // ldi r16,0x2a + var e = new AvrAssembler (); + Assert.That (Bytes ("05e5"), Is.EqualTo (e.Assemble (".equ bar, 0x55\nldi r16, bar"))); // ldi r16,0x55 + // the Atmel '=' form still works + var eq = new AvrAssembler (); + Assert.That (Bytes ("00e1"), Is.EqualTo (eq.Assemble (".equ baz = 0x10\nldi r16, baz"))); + } + + [Test(Description = ".byte/.word with a forward-referenced symbol expression defers to pass two")] + public void DataDirective_ForwardSymbol () + { + // x at byte 2: lo8(x)=0x02, hi8(x)=0x00; then a NOP + var b = new AvrAssembler (); + var rb = b.Assemble (".byte lo8(x), hi8(x)\nx: nop"); + Assert.That (b.Errors, Is.Empty); + Assert.That (Bytes ("0200" + "0000"), Is.EqualTo (rb)); + + // jump-table style: .word of forward labels (a=byte4, b=byte6) then two NOPs + var w = new AvrAssembler (); + var rw = w.Assemble (".word a, b\na: nop\nb: nop"); + Assert.That (w.Errors, Is.Empty); + Assert.That (Bytes ("0400" + "0600" + "0000" + "0000"), Is.EqualTo (rw)); + } + + [Test(Description = "__gcc_isr gives an actionable error pointing to -mno-gas-isr-prologues")] + public void GccIsr_ActionableError () + { + var assembler = new AvrAssembler (); + assembler.Assemble ("__vector_1:\n__gcc_isr 1\nreti"); + + Assert.That (assembler.Errors, Is.Not.Empty); + Assert.That (assembler.Errors[0], Does.Contain ("-mno-gas-isr-prologues")); + } + // ----------------------------------------------------------------------- // avr-gcc / avr-as front-end compatibility // -----------------------------------------------------------------------