/
go.go
345 lines (329 loc) · 10.7 KB
/
go.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package main
import (
"os"
"fmt"
"strings"
"strconv"
"unicode"
"go/ast"
"go/token"
"go/parser"
"go/typechecker"
"github.com/droundy/go/elf"
"github.com/droundy/go/x86"
"github.com/droundy/goopt"
)
var myfiles = token.NewFileSet()
type StringVisitor CompileVisitor
func (v StringVisitor) Visit(n0 ast.Node) (w ast.Visitor) {
//fmt.Printf("in StringVisitor, n0 is %s of type %T\n", n0, n0)
if n,ok := n0.(*ast.BasicLit); ok && n != nil && n.Kind == token.STRING {
str,err := strconv.Unquote(string(n.Value))
if err != nil {
panic(err)
}
sanitize := func(rune int) int {
if unicode.IsLetter(rune) {
return rune
}
return -1
}
fmt.Println("string literals are: ", v.string_literals)
if _,ok := v.string_literals[str]; !ok {
strname := "string_" + strings.Map(sanitize, str)
for {
// See if our strname is valid...
nameexists := false
for _,n := range v.string_literals {
if n == strname {
nameexists = true
}
}
if !nameexists {
break // we've got a unique name already!
}
strname = strname + "X"
}
*v.assembly = append(*v.assembly,
x86.Symbol(strname),
x86.Commented(x86.Ascii(str), "a non-null-terminated string"))
v.string_literals[str] = strname
fmt.Println("Got new string literal: ", str)
}
}
return v
}
type CompileVisitor struct {
assembly *[]x86.X86
string_literals map[string]string
Stack *Stack
}
func (v *CompileVisitor) Append(xs... x86.X86) {
*v.assembly = append(*v.assembly, xs...)
}
func (v *CompileVisitor) FunctionPrologue(fn *ast.FuncDecl) {
v.Stack = v.Stack.New(fn.Name.Name)
ftype := ast.NewType(ast.Function)
ftype.N = uint(fn.Type.Results.NumFields())
ftype.Params = ast.NewScope(nil)
fmt.Println("Working on function", fn.Name.Name)
if fn.Type.Results != nil {
resultnum := 0
for _,resultfield := range fn.Type.Results.List {
names := []string{"_"}
if resultfield.Names != nil {
names = []string{}
for _,i := range resultfield.Names {
names = append(names, i.Name)
}
}
t := TypeExpression(resultfield.Type)
for _,n := range names {
ftype.Params.Insert(&ast.Object{ ast.Fun, n, t, resultfield, 0 })
resultnum++
v.Stack.DefineVariable(n, t, fmt.Sprintf("return_value_%d", resultnum))
// The return values are actually allocated elsewhere... here
// we just need to define the function type properly so it
// gets called properly.
}
}
}
fmt.Println("Stack size after results is", v.Stack.Size)
v.Stack.ReturnSize = v.Stack.Size
// The arguments are pushed last argument first, so that eventually
// the types of the "later" arguments can depend on the first
// arguments, which seems nice to me.
for pi:=len(fn.Type.Params.List)-1; pi>=0; pi-- {
paramfield := fn.Type.Params.List[pi]
names := []string{"_"}
if paramfield.Names != nil {
names = []string{}
for _,i := range paramfield.Names {
names = append(names, i.Name)
}
}
t := TypeExpression(paramfield.Type)
for i:=len(names)-1; i>=0; i-- {
n := names[i]
ftype.Params.Insert(&ast.Object{ ast.Fun, n, t, paramfield, 0 })
v.Stack.DefineVariable(n, t)
// The function parameters are actually allocated
// elsewhere... here we just need to define the function type
// properly so it gets called properly.
}
}
fmt.Println("Stack size after params is", v.Stack.Size)
v.Stack.DefineVariable("return", IntType)
fmt.Println("Stack size after return is", v.Stack.Size)
v.Stack = v.Stack.New("_")
DefineGlobal(fn.Name.Name, ftype)
// symbol for the start name
pos := myfiles.Position(fn.Pos())
v.Append(x86.Commented(x86.GlobalSymbol("main_"+fn.Name.Name),
fmt.Sprint(pos.Filename, ": line ", pos.Line)))
// If we had arguments, we'd want to swap them with the return
// address here...
}
func (v *CompileVisitor) FunctionPostlogue() {
// First we roll back the stack from where we started...
for v.Stack.Name == "_" {
// We need to pop off any extra layers of stack we've added...
if v.Stack.Size > 0 {
v.Append(x86.Commented(x86.AddL(x86.Imm32(v.Stack.Size), x86.ESP),
"We stored this much on the stack so far."))
}
v.Stack = v.Stack.Parent // We've popped off the arguments...
}
// Now jump to the "real" postlogue. This is a little stupid, but I
// expect it'll come in handy when I implement defer (not to mention
// panic/recover).
v.Append(x86.Jmp(x86.Symbol("return_" + v.Stack.Name)))
}
func (v *CompileVisitor) Visit(n0 ast.Node) (w ast.Visitor) {
// The following only handles functions (not methods)
if n,ok := n0.(*ast.FuncDecl); ok && n.Recv == nil {
v.FunctionPrologue(n)
for _,statement := range n.Body.List {
v.CompileStatement(statement)
}
v.FunctionPostlogue()
v.Append(x86.GlobalSymbol("return_"+n.Name.Name))
v.Append(x86.Commented(x86.PopL(x86.EAX), "Pop the return address"))
// Pop off function arguments...
fmt.Println("Function", v.Stack.Name, "has stack size", v.Stack.Size)
fmt.Println("Function", v.Stack.Name, "has return values size", v.Stack.ReturnSize)
v.Append(x86.Commented(x86.AddL(x86.Imm32(v.Stack.Size - 4 - v.Stack.ReturnSize), x86.ESP),
"Popping "+v.Stack.Name+" arguments."))
// Then we return!
v.Append(x86.RawAssembly("\tjmp *%eax"))
v.Stack = v.Stack.Parent
return nil // No need to peek inside the func declaration!
}
return v
}
func (v *CompileVisitor) PopType(t ast.Type) {
switch t.Form {
case ast.Tuple:
for _,o := range t.Params.Objects {
v.PopType(*o.Type)
}
case ast.Basic:
switch t.N {
case ast.String:
v.Append(x86.AddL(x86.Imm32(8), x86.ESP))
default:
panic(fmt.Sprintf("I don't know how to pop basic type %s", t))
}
default:
panic(fmt.Sprintf("I don't know how to pop type %s", t.Form))
}
}
func (v *CompileVisitor) CompileStatement(statement ast.Stmt) {
switch s := statement.(type) {
case *ast.EmptyStmt:
// It is empty, I can handle that!
case *ast.ExprStmt:
v.CompileExpression(s.X)
t := ExprType(s.X, v.Stack)
switch t.Form {
case ast.Tuple:
}
case *ast.ReturnStmt:
for resultnum,e := range s.Results {
vname := fmt.Sprintf("return_value_%d", resultnum+1)
v.CompileExpression(e)
v.PopTo(vname)
}
v.FunctionPostlogue()
default:
panic(fmt.Sprintf("I can't handle statements such as: %T", statement))
}
}
func (v *CompileVisitor) CompileExpression(exp ast.Expr) {
switch e := exp.(type) {
case *ast.BasicLit:
switch e.Kind {
case token.STRING:
str,err := strconv.Unquote(string(e.Value))
if err != nil {
panic(err)
}
n,ok := v.string_literals[str]
if !ok {
panic(fmt.Sprintf("I don't recognize the string: %s", string(e.Value)))
}
v.Append(
x86.Commented(x86.PushL(x86.Symbol(n)), "Pushing string literal "+string(e.Value)),
x86.PushL(x86.Imm32(len(str))))
v.Stack.Push(StringType) // let the stack know we've pushed a literal
default:
panic(fmt.Sprintf("I don't know how to deal with literal: %s", e))
}
case *ast.CallExpr:
if fn,ok := e.Fun.(*ast.Ident); ok {
pos := myfiles.Position(fn.Pos())
switch fn.Name {
case "println", "print":
if len(e.Args) != 1 {
panic(fmt.Sprintf("%s expects just one argument, not %d", fn.Name, len(e.Args)))
}
argtype := ExprType(e.Args[0], v.Stack)
if argtype.N != ast.String || argtype.Form != ast.Basic {
panic(fmt.Sprintf("Argument to %s has type %s but should have type string!",
fn.Name, argtype))
}
v.Stack = v.Stack.New("arguments")
v.CompileExpression(e.Args[0])
v.Append(x86.Commented(x86.Call(x86.Symbol(fn.Name)),
fmt.Sprint(pos.Filename, ": line ", pos.Line)))
v.Stack = v.Stack.Parent // A hack to let the callee clean up arguments
default:
// This must not be a built-in function...
functype := v.Stack.Lookup(fn.Name)
if functype.Type().Form != ast.Function {
panic("Function "+ fn.Name + " is not actually a function!")
}
for i:=0; i<int(functype.Type().N); i++ {
// Put zeros on the stack for the return values (and define these things)
v.Declare(functype.Type().Params.Objects[i].Name,
functype.Type().Params.Objects[i].Type)
}
v.Stack = v.Stack.New("arguments")
for i:=len(e.Args)-1; i>=0; i-- {
v.CompileExpression(e.Args[i])
}
// FIXME: I assume here that there is no return value!
v.Append(x86.Commented(x86.Call(x86.Symbol("main_"+fn.Name)),
fmt.Sprint(pos.Filename, ": line ", pos.Line)))
v.Stack = v.Stack.Parent // A hack to let the callee clean up arguments
}
} else {
panic(fmt.Sprintf("I don't know how to deal with complicated function: %s", e.Fun))
}
case *ast.Ident:
evar := v.Stack.Lookup(e.Name)
switch SizeOnStack(evar.Type()) {
case 4:
v.Append(x86.Commented(x86.MovL(evar.InMemory(), x86.EAX), "Reading variable "+e.Name))
v.Append(x86.PushL(x86.EAX))
v.Stack.DefineVariable("_copy_of_"+e.Name, evar.Type())
case 8:
v.Append(x86.Comment(fmt.Sprintf("The offset of %s is %s", e.Name, evar.InMemory())))
v.Append(x86.Commented(x86.MovL(evar.InMemory().Add(4), x86.EAX),
"Reading variable "+e.Name))
v.Append(x86.MovL(evar.InMemory(), x86.EBX))
v.Append(x86.PushL(x86.EAX))
v.Append(x86.PushL(x86.EBX))
v.Stack.DefineVariable("_copy_of_"+e.Name, evar.Type())
default:
panic(fmt.Sprintf("I don't handle variables with length %s", SizeOnStack(evar.Type())))
}
default:
panic(fmt.Sprintf("I can't handle expressions such as: %T value %s", exp, exp))
}
}
func (v *CompileVisitor) PopTo(vname string) {
v.Append(v.Stack.PopTo(vname))
}
func (v *CompileVisitor) Declare(vname string, t *ast.Type) {
switch SizeOnStack(t) {
case 4:
v.Append(x86.Commented(x86.PushL(x86.Imm32(0)), "This is variable "+vname))
case 8:
v.Append(x86.Commented(x86.PushL(x86.Imm32(0)), "This is variable "+vname))
v.Append(x86.Commented(x86.PushL(x86.Imm32(0)), "This is variable "+vname))
default:
panic(fmt.Sprintf("I don't handle variables with length %s", SizeOnStack(t)))
}
v.Stack.DefineVariable(vname, t)
}
func main() {
goopt.Parse(func() []string { return nil })
if len(goopt.Args) > 0 {
x,err := parser.ParseFiles(myfiles, goopt.Args, 0)
die(err)
fmt.Fprintln(os.Stderr, "Parsed: ", *x["main"])
die(typechecker.CheckPackage(myfiles, x["main"], nil))
fmt.Fprintln(os.Stderr, "Checked: ", *x["main"])
//for _,a := range x["main"].Files {
// die(printer.Fprint(os.Stdout, a))
//}
aaa := x86.StartData
var bbb *Stack
var cv = CompileVisitor{ &aaa, make(map[string]string), bbb.New("global")}
ast.Walk(StringVisitor(cv), x["main"])
cv.Append(x86.StartText...)
ast.Walk(&cv, x["main"])
// Here we just add a crude debug library
cv.Append(x86.Debugging...)
ass := x86.Assembly(*cv.assembly)
//fmt.Println(ass)
die(elf.AssembleAndLink(goopt.Args[0][:len(goopt.Args[0])-3], []byte(ass)))
}
}
func die(err os.Error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}