Hello again,
I'm trying to create a template that substitutes a proc in a loop, so I can create the same proc with minor variations.
This is the relevant snippet:
const erroringVectors = [8, 10, 11, 12, 13, 14, 17, 21, 29, 30]
var interruptProcedures: array[32, uint64]
template interproc(interruptVector, wrapper) =
proc vectorPusher() {.exportc, gensym, asmNoStackFrame.} =
asm """
pushq `interruptVector`
jmp `wrapper`
"""
interruptProcedures[`interruptVector`] = cast[uint64](vectorPusher)
for iv in 0..<32:
var wrapperProcName: string = if iv in erroringVectors: "errorInterruptWrapper" else: "genericInterruptWrapper"
interproc(iv, wrapperProcName)
I have two issues:
With regards to 1), if I use gensym on the proc my compiler screams, and if I try to define it as a var like so:
template interproc(interruptVector, name, wrapper) =
proc `name`() {.exportc, asmNoStackFrame.} =
....
for iv in 0..<32:
var wrapperProcName: string = if iv in erroringVectors: "errorInterruptWrapper" else: "genericInterruptWrapper"
var procname: string = "vectorPusher" & $iv
interproc(iv, procname, wrapperProcName)
I get an error at this line proc `name`() {.exportc, asmNoStackFrame.} = telling me procname was already defined at this line: var procname: string = "vectorPusher" & $iv which is very odd.
I would be grateful for any help.
With this snippet:
import macros
...
macro interproc(interruptVector, name, wrapper) = quote do:
proc `name`() {.exportc, asmNoStackFrame.} =
asm """
pushq `interruptVector`
jmp `wrapper`
"""
interruptProcedures[`interruptVector`] = cast[uint64](`name`)
for iv in 0..<32:
var wrapperProcName: string = if iv in erroringVectors: "errorInterruptWrapper" else: "genericInterruptWrapper"
var procname: string = "vectorPusher" & $iv
interproc(iv, procname, wrapperProcName)
I get the following result:
kernel/idt.nim(114, 12) template/generic instantiation of `interproc` from here
kernel/idt.nim(103, 51) Error: redefinition of 'procname'; previous declaration here: kernel/idt.nim(113, 7)
Line 103 is the macro prototype, line 113 is the procname declaration, line 114 is the macro call.
Thanks for the hint, after a few hours breaking my teeth on it I got something working but it's really ugly:
var interruptProcedures: array[32, uint64]
macro interproc() =
result = newStmtList()
for iv in 0..<32:
let wrapperName = if iv in erroringVectors: "errorInterruptWrapper" else: "genericInterruptWrapper"
let name = ident("vectorPusher" & $iv)
let vector = newLit(iv)
let wrapper = ident(wrapperName)
let asmCode = newNimNode(nnkAsmStmt).add(newEmptyNode()).add(newLit("pushq " & $iv & ";jmp " & wrapperName))
result.add quote do:
proc `name`() {.exportc, asmNoStackFrame.} =
`asmCode`
interruptProcedures[`vector`] = cast[uint64](`name`)
return result
interproc()
I couldn't figure out how to use quote effectively.
In one of my many attempts I tried this:
macro foobar() =
let myLiteral = newLit("push %rax")
result = quote do:
proc foo() =
asm `myLiteral`
Which returns the following error: /usercode/in.nim(9, 11) Error: the 'asm' statement takes a string literal I don't know if it's a bug or a known limitation.
I also found this post: https://forum.nim-lang.org/t/8279 which was very useful in finding out what root node I should be using.
I feel like this would be a great additional example for this tutorial https://nim-lang.org/docs/tut3.html since it shows looping in macros, defining proc names dynamically, building statements iteratively, and using ident and newLit. All of which are a bit obscure currently, from my perspective as a beginner.
Something like:
import macros
macro createProcedures() =
result = newStmtList()
for i in 0..<10:
let name = ident("myProc" & $i)
let content = newLit("I am procedure number #" & $i)
result.add quote do:
proc `name`() {.exportc.} =
echo `content`
return result
createProcedures()
myProc7()
That example would indeed be great for the tutorial, please create a PR adding it!
The macro you've created works fine, but as you mentioned that you didn't like the way it looked I took a stab at improving it:
import macros
macro interproc(): untyped =
const erroringVectors = [8, 10, 11, 12, 13, 14, 17, 21, 29, 30]
# Create a statement list with a single [] element
result = newStmtList(nnkBracket.newTree())
for iv in 0..<32:
let
wrapperName = if iv in erroringVectors: "errorInterruptWrapper" else: "genericInterruptWrapper"
name = ident("vectorPusher" & $iv)
asmCode = nnkAsmStmt.newTree(
newEmptyNode(),
newLit("pushq " & $iv & ";jmp " & wrapperName))
# Add proc to the statement list
result.insert 0, quote do:
proc `name`() {.exportc, asmNoStackFrame.} =
`asmCode`
# Add pointer to proc in the array
result[^1].add quote do:
cast[uint64](`name`)
# `result` is implicitly returned and is now an an array of uint64s which can be assigned to a variable
let interruptProcedures = interproc()
Whether or not it's an improvement is entirely subjective of course, but at least to me the creation of an array which can be directly assigned to any variable makes the invocation of the macro easier to read.