-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Emitting ifdefs and friends #235
Comments
As a temp workaround I made a temp script that uses comments to contain the ifdefs. |
Thanks for posting this issue, and thank you @pelletier for the PR #406. We should find a way to support I am uneasy about the approach taken in #406 since it allows emitting preprocessor text into an avo program without accounting for the semantics. Specifically, an assembly program with preprocessor conditionals actually represents multiple possible assembly programs, whereas The following pathological //go:build ignore
package main
import (
. "github.com/mmcloughlin/avo/build"
. "github.com/mmcloughlin/avo/operand"
. "github.com/mmcloughlin/avo/reg"
)
func main() {
const n = 7
TEXT("Preprocessor", NOSPLIT, "func() uint64")
// Allocate n registers with values 1, 2, ..., n.
Preprocessor("ifdef CONDITION")
x := make([]GPVirtual, n)
for i := 0; i < n; i++ {
x[i] = GP64()
MOVQ(U32(1+i), x[i])
}
Preprocessor("else")
y := make([]GPVirtual, n)
for i := 0; i < n; i++ {
y[i] = GP64()
MOVQ(U32(1+i), y[i])
}
Preprocessor("endif")
// Sum them up.
Preprocessor("ifdef CONDITION")
for i := 1; i < n; i++ {
ADDQ(x[i], x[0])
}
Store(x[0], ReturnIndex(0))
Preprocessor("else")
for i := 1; i < n; i++ {
ADDQ(y[i], y[0])
}
Store(y[0], ReturnIndex(0))
Preprocessor("endif")
RET()
Generate()
} The generated assembly is as follows. Note how // Code generated by command: go run asm.go -out preprocessor.s -stubs stub.go. DO NOT EDIT.
#include "textflag.h"
// func Preprocessor() uint64
TEXT ·Preprocessor(SB), NOSPLIT, $0-8
#ifdef CONDITION
MOVQ $0x00000001, AX
MOVQ $0x00000002, CX
MOVQ $0x00000003, DX
MOVQ $0x00000004, BX
MOVQ $0x00000005, SI
MOVQ $0x00000006, DI
MOVQ $0x00000007, R8
#else
MOVQ $0x00000001, R9
MOVQ $0x00000002, R10
MOVQ $0x00000003, R11
MOVQ $0x00000004, R12
MOVQ $0x00000005, R13
MOVQ $0x00000006, R14
MOVQ $0x00000007, R15
#endif
#ifdef CONDITION
ADDQ CX, AX
ADDQ DX, AX
ADDQ BX, AX
ADDQ SI, AX
ADDQ DI, AX
ADDQ R8, AX
MOVQ AX, ret+0(FP)
#else
ADDQ R10, R9
ADDQ R11, R9
ADDQ R12, R9
ADDQ R13, R9
ADDQ R14, R9
ADDQ R15, R9
MOVQ R9, ret+0(FP)
#endif
RET I'm not sure how frequent this kind of problem would be in reality, but I just wanted to demonstrate why I felt uncomfortable about an approach that essentially just emits text into the program. I'm also not quite sure yet what the right approach to this would be. |
@mmcloughlin Thanks for taking a look! For context I mainly use it for
This is fairly straightforward and I "emit" it like this:
... and the script removes My second use case is a bit more complicated... And a lot more hackish...
So I emit a dummy function with the ifdefs as comments and overload and existing operation with a similar signature... data = bytes.ReplaceAll(data, []byte("\t// #"), []byte("#"))
data = bytes.ReplaceAll(data, []byte("\t// @"), []byte(""))
data = bytes.ReplaceAll(data, []byte("VPTERNLOGQ"), []byte("XOR3WAY("))
/// Patch up missing `)` I could just emit 2 versions of the code with build tags, but I already have ~5MB of assembly output - so I am not really looking to make it any more if it can be avoided. So not having these hacks would be great. |
Thank you for the feedback! I wonder if we could use Go functions to represent the multiple versions of the program. For example: func main() {
const n = 7
TEXT("Preprocessor", NOSPLIT, "func() uint64")
Preprocessor("ifdef CONDITION")
Guard("CONDITION", func(c *avo.Context) { // if
x := make([]GPVirtual, n)
for i := 0; i < n; i++ {
x[i] = c.GP64()
MOVQ(U32(1+i), x[i])
}
for i := 1; i < n; i++ {
ADDQ(x[i], x[0])
}
c.Store(x[0], c.ReturnIndex(0))
}, func(c *avo.Context) { // else
y := make([]GPVirtual, n)
for i := 0; i < n; i++ {
y[i] = c.GP64()
MOVQ(U32(1+i), y[i])
}
for i := 1; i < n; i++ {
ADDQ(y[i], y[0])
}
c.Store(y[0], c.ReturnIndex(0))
})
// global context is left untouched.
RET()
Generate()
} But that feels error-prone to mistakenly use the global context instead of the local context in those functions. Or maybe the solution needs to be more targeted if only register allocations is the issue? something like: func main() {
const n = 7
TEXT("Preprocessor", NOSPLIT, "func() uint64")
// Allocate n registers with values 1, 2, ..., n.
Preprocessor("ifdef CONDITION")
// BranchAllocator would be a new function that creates a new register
// allocator that starts with the same state as the global allocator,
// but effectively "branches out".
x := make([]GPVirtual, n)
allocatorX := BranchAllocator()
for i := 0; i < n; i++ {
x[i] = allocatorX.GP64()
MOVQ(U32(1+i), x[i])
}
Preprocessor("else")
y := make([]GPVirtual, n)
allocatorY := BranchAllocator()
for i := 0; i < n; i++ {
y[i] = allocatorY.GP64()
MOVQ(U32(1+i), y[i])
}
Preprocessor("endif")
// Sum them up.
Preprocessor("ifdef CONDITION")
for i := 1; i < n; i++ {
ADDQ(x[i], x[0])
}
Store(x[0], ReturnIndex(0))
Preprocessor("else")
for i := 1; i < n; i++ {
ADDQ(y[i], y[0])
}
Store(y[0], ReturnIndex(0))
Preprocessor("endif")
// At any point later, if you need to allocate you have two options:
// Option 1: pick the allocator that represents the version of the
// program you care about:
reg := allocatorX.GP64()
// Option 2: forget about those allocations and use the global
// allocator.
reg := GP64()
RET()
Generate()
} In any case, it will probably take quite a bit to get to a satisfying solution to this problem. In the meantime, how would you feel about adding an escape-hatch function that allows emitting arbitrary text? Something like |
Thanks both! @pelletier I have not fully thought through the implications of having an At this point I'm leaning towards just adding functions Ifdef("CONDITION")
// ...
Else()
// ...
Endif() Some alternatives are things like With a design like this it will also be fairly easy for @klauspost Your first example seems like the simple case, but I'm not sure about the second. I'm hesitant to add support for |
Sounds good to me. I'll submit a new PR that adds |
My only other question about #406 is whether we should accept I'm wondering because Possible alternatives:
Thoughts? |
I think |
Hi!
Thanks for the great work. To support GOAMD64 I would like to emit:
There are no build tags for GOAMD64, and in this case it would duplicate 15K lines of assembly, if I was to have runtime detection, so an ifdef would be fine in this case.
Unless I am missing something that is currently not possible.
The text was updated successfully, but these errors were encountered: