Skip to content

Commit 020f3fe

Browse files
pyrex41Reuben Brooksclaude
authored
Extract bytecode-IR->Go translator into an importable codegen package (#52)
* Extract bytecode-IR->Go translator into importable codegen package cmd/kl's bc->go native now wraps codegen.CodeGenerator so other build tools (the upcoming cmd/yggdrasil-build) can drive the translator in-process without going through KL. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * codegen: add HandleBodyObj for in-memory bytecode IR HandleBody reads bytecode IR by re-parsing printed text; for large chunks the print-to-string path through the kernel printer is superlinear (a ~300KB chunk costs minutes). HandleBodyObj takes the IR object straight off the VM and feeds the same generation path, making programmatic (non-file) compilation pipelines practical: measured 494s -> 4.5s generating Go for a ~70KB KL program. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Reuben Brooks <reuben.brooks@facilitygrid.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 343d665 commit 020f3fe

2 files changed

Lines changed: 415 additions & 360 deletions

File tree

cmd/kl/codegen.go

Lines changed: 8 additions & 360 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
package main
22

3+
// KL-native bindings over the shared bytecode-IR -> Go translator (package
4+
// codegen). The translator itself lives in codegen/ so other build tools
5+
// (cmd/yggdrasil-build) can drive it without going through KL.
6+
37
import (
4-
"bytes"
5-
"errors"
6-
"fmt"
7-
"io"
88
"os"
99
"unsafe"
1010

11+
"github.com/tiancaiamao/shen-go/codegen"
1112
"github.com/tiancaiamao/shen-go/kl"
1213
)
1314

1415
var makeCodeGenerator = kl.MakeNative(func(e *kl.ControlFlow) {
1516
// (make-code-generator 'cora)
16-
cg := &codeGenerator{
17-
declare: make(map[kl.Obj]struct{}),
18-
}
19-
e.Return(kl.MakeRaw(&cg.scmHead))
17+
cg := codegen.New()
18+
e.Return(kl.MakeRaw(&cg.ScmHead))
2019

2120
}, 0)
2221

2322
var bcToGo = kl.MakeNative(func(e *kl.ControlFlow) {
2423
// (let cg (make-code-generator)
2524
// (bc->go cg "Main" true "xx.bc" "xx.go"))
26-
cg := (*codeGenerator)(unsafe.Pointer(e.Get(1)))
25+
cg := (*codegen.CodeGenerator)(unsafe.Pointer(e.Get(1)))
2726
exportName := kl.GetString(e.Get(2))
2827
genSym := e.Get(3)
2928
inFile := kl.GetString(e.Get(4))
@@ -54,354 +53,3 @@ var bcToGo = kl.MakeNative(func(e *kl.ControlFlow) {
5453
e.Return(kl.Nil)
5554

5655
}, 5)
57-
58-
type codeGenerator struct {
59-
scmHead int
60-
declare map[kl.Obj]struct{}
61-
}
62-
63-
func (cg *codeGenerator) HandleBody(f io.Reader, export string, out io.Writer) error {
64-
fmt.Fprintf(out, "package main\n\n")
65-
fmt.Fprintf(out, "import . \"github.com/tiancaiamao/shen-go/kl\"\n\n")
66-
fmt.Fprintf(out, `var %s = MakeNative(func(__e *ControlFlow) {
67-
`, export)
68-
r := kl.NewSexpReader(f, false)
69-
bc, err := r.Read()
70-
if err != nil {
71-
fmt.Println("read bytecode error", err)
72-
return err
73-
}
74-
if err := cg.generateExpr(out, bc); err != nil {
75-
return err
76-
}
77-
fmt.Fprintf(out, "\n\n")
78-
fmt.Fprintf(out, "}, 0)\n\n")
79-
return nil
80-
}
81-
82-
func (cg *codeGenerator) HandleSymbol(out io.Writer) {
83-
for sym := range cg.declare {
84-
symStr := kl.GetSymbol(sym)
85-
symVar := "sym" + symbolAsVar(sym)
86-
fmt.Fprintf(out, "var %s = MakeSymbol(\"%s\")\n", symVar, symStr)
87-
}
88-
}
89-
90-
func symbolAsVar(sym kl.Obj) string {
91-
str := kl.GetSymbol(sym)
92-
var buf bytes.Buffer
93-
for i := 0; i < len(str); i++ {
94-
switch str[i] {
95-
case '_':
96-
buf.WriteString("__")
97-
case '-':
98-
buf.WriteString("_1")
99-
case '?':
100-
buf.WriteString("_2")
101-
case '$':
102-
buf.WriteString("_3")
103-
case '.':
104-
buf.WriteString("_4")
105-
case '<':
106-
buf.WriteString("_5")
107-
case '>':
108-
buf.WriteString("_6")
109-
case '+':
110-
buf.WriteString("_7")
111-
case '@':
112-
buf.WriteString("_8")
113-
case '=':
114-
buf.WriteString("_a")
115-
case '!':
116-
buf.WriteString("_b")
117-
case '/':
118-
buf.WriteString("_c")
119-
case '*':
120-
buf.WriteString("_d")
121-
case '&':
122-
buf.WriteString("_e")
123-
case '%':
124-
buf.WriteString("_f")
125-
case '^':
126-
buf.WriteString("_g")
127-
case ':':
128-
buf.WriteString("_h")
129-
case '{':
130-
buf.WriteString("_i")
131-
case '}':
132-
buf.WriteString("_j")
133-
case ';':
134-
buf.WriteString("_k")
135-
case ',':
136-
buf.WriteString("_l")
137-
default:
138-
buf.WriteByte(str[i])
139-
}
140-
}
141-
return buf.String()
142-
}
143-
144-
func (cg *codeGenerator) generateExpr(w io.Writer, sexp kl.Obj) error {
145-
// fmt.Printf("handle %s ..\n", ObjString(sexp))
146-
if kl.IsSymbol(sexp) {
147-
fmt.Fprintf(w, "%s", symbolAsVar(sexp))
148-
return nil
149-
}
150-
kind := kl.GetSymbol(kl.Car(sexp))
151-
switch kind {
152-
case "block":
153-
for cur := kl.Cdr(sexp); cur != kl.Nil; cur = kl.Cdr(cur) {
154-
p := kl.Car(cur)
155-
if err := cg.generateExpr(w, p); err != nil {
156-
return err
157-
}
158-
fmt.Fprintln(w)
159-
}
160-
fmt.Fprintln(w)
161-
case "<-":
162-
// (<- a b)
163-
a := kl.Car(kl.Cdr(sexp))
164-
b := kl.Car(kl.Cdr(kl.Cdr(sexp)))
165-
fmt.Fprintf(w, "%s = ", symbolAsVar(a))
166-
cg.generateExpr(w, b)
167-
fmt.Fprintln(w)
168-
case "<=":
169-
a := kl.Car(kl.Cdr(sexp))
170-
b := kl.Car(kl.Cdr(kl.Cdr(sexp)))
171-
fmt.Fprintf(w, "%s := ", symbolAsVar(a))
172-
cg.generateExpr(w, b)
173-
fmt.Fprintln(w)
174-
case "$global":
175-
sym := kl.Car(kl.Cdr(sexp))
176-
cg.declare[sym] = struct{}{}
177-
fmt.Fprintf(w, "PrimFunc(sym%s)", symbolAsVar(sym))
178-
case "$const":
179-
// (const Number)
180-
// (const ())
181-
// (const "xxx")
182-
c := kl.Cadr(sexp)
183-
if err := cg.generateConst(w, c); err != nil {
184-
return err
185-
}
186-
case "lambda":
187-
// (lambda (p1 p2 ...) ...)
188-
tmp := kl.Car(kl.Cdr(sexp))
189-
args := kl.ListToSlice(tmp)
190-
fmt.Fprintf(w, "MakeNative(func(__e *ControlFlow) {\n")
191-
for i, arg := range args {
192-
fmt.Fprintf(w, "%s := __e.Get(%d)\n", symbolAsVar(arg), i+1)
193-
fmt.Fprintf(w, "_ = %s\n", symbolAsVar(arg))
194-
}
195-
if err := cg.generateExpr(w, kl.Car(kl.Cdr(kl.Cdr(sexp)))); err != nil {
196-
return err
197-
}
198-
fmt.Fprintf(w, "}, %d)", len(args))
199-
case "if":
200-
// (if a b c)
201-
a := kl.Cadr(sexp)
202-
b := kl.Car(kl.Cdr(kl.Cdr(sexp)))
203-
c := kl.Car(kl.Cdr(kl.Cdr(kl.Cdr(sexp))))
204-
if err := cg.generateIfExpr(w, a, b, c); err != nil {
205-
return err
206-
}
207-
case "ignore": // (ignore xx)
208-
fmt.Fprintf(w, "_ = ")
209-
exp := kl.Car(kl.Cdr(sexp))
210-
cg.generateExpr(w, exp)
211-
fmt.Fprintln(w)
212-
case "var":
213-
// (var xxx)
214-
sym := kl.Car(kl.Cdr(sexp))
215-
fmt.Fprintf(w, "var %s Obj\n", symbolAsVar(sym))
216-
case "return":
217-
val := kl.Cadr(sexp)
218-
fmt.Fprintf(w, "__e.Return(")
219-
cg.generateExpr(w, val)
220-
fmt.Fprintf(w, ")\nreturn\n")
221-
case "call":
222-
// (call f a b c ...)
223-
ok, err := cg.primitiveCallOptimize(w, sexp, false)
224-
if err != nil {
225-
return err
226-
}
227-
if ok {
228-
return nil
229-
}
230-
231-
fmt.Fprintf(w, "Call(__e, ")
232-
args := kl.ListToSlice(kl.Cdr(sexp))
233-
for i, arg := range args {
234-
if i != 0 {
235-
fmt.Fprintf(w, ", ")
236-
}
237-
if kl.IsSymbol(arg) {
238-
fmt.Fprintf(w, "%s", symbolAsVar(arg))
239-
} else {
240-
if err := cg.generateExpr(w, arg); err != nil {
241-
return err
242-
}
243-
}
244-
}
245-
fmt.Fprintf(w, ")\n")
246-
case "tailapply":
247-
// (tailapply f a b c ...)
248-
ok, err := cg.primitiveCallOptimize(w, sexp, true)
249-
if err != nil {
250-
return err
251-
}
252-
if ok {
253-
return nil
254-
}
255-
256-
fmt.Fprintf(w, "__e.TailApply(")
257-
args := kl.ListToSlice(kl.Cdr(sexp))
258-
for i, arg := range args {
259-
if i != 0 {
260-
fmt.Fprintf(w, ", ")
261-
}
262-
if kl.IsSymbol(arg) {
263-
fmt.Fprintf(w, "%s", symbolAsVar(arg))
264-
} else {
265-
if err := cg.generateExpr(w, arg); err != nil {
266-
return err
267-
}
268-
}
269-
}
270-
fmt.Fprintf(w, ")\nreturn\n")
271-
default:
272-
return fmt.Errorf("unknown instruct: %s", kind)
273-
}
274-
return nil
275-
}
276-
277-
func (cg *codeGenerator) generateConst(w io.Writer, c kl.Obj) error {
278-
switch {
279-
case kl.IsNumber(c):
280-
fmt.Fprintf(w, "MakeNumber(%d)", kl.GetInteger(c))
281-
case kl.IsString(c):
282-
str := kl.GetString(c)
283-
fmt.Fprintf(w, "MakeString(%#v)", str)
284-
case kl.IsSymbol(c):
285-
cg.declare[c] = struct{}{}
286-
fmt.Fprintf(w, "sym%s", symbolAsVar(c))
287-
case c == kl.Nil:
288-
fmt.Fprintf(w, "Nil")
289-
case c == kl.True:
290-
fmt.Fprintf(w, "True")
291-
case c == kl.False:
292-
fmt.Fprintf(w, "False")
293-
default:
294-
return errors.New("unknown $const instruct")
295-
}
296-
return nil
297-
}
298-
299-
func (cg *codeGenerator) generateIfExpr(w io.Writer, a, b, c kl.Obj) error {
300-
fmt.Fprintf(w, "if True == ")
301-
if err := cg.generateExpr(w, a); err != nil {
302-
return err
303-
}
304-
fmt.Fprintf(w, " {\n")
305-
if err := cg.generateExpr(w, b); err != nil {
306-
return err
307-
}
308-
fmt.Fprintf(w, "} else {\n")
309-
if err := cg.generateExpr(w, c); err != nil {
310-
return err
311-
}
312-
fmt.Fprintf(w, "}\n")
313-
return nil
314-
}
315-
316-
var symGlobal = kl.MakeSymbol("$global")
317-
var symConst = kl.MakeSymbol("$const")
318-
319-
func (cg *codeGenerator) primitiveCallOptimize(w io.Writer, sexp kl.Obj, tail bool) (bool, error) {
320-
// (call ($global XX) ...)
321-
// (tailapply ($global XX) ...)
322-
fn := kl.Cadr(sexp)
323-
if kl.PrimIsPair(fn) == kl.False || kl.Car(fn) != symGlobal {
324-
return false, nil
325-
}
326-
global := kl.Cadr(fn)
327-
str := kl.GetSymbol(global)
328-
args := kl.ListToSlice(kl.Cdr(kl.Cdr(sexp)))
329-
prim, ok := shenPrimitive[str]
330-
if !ok || prim.Arity != len(args) {
331-
return false, nil
332-
}
333-
primName := prim.Name
334-
335-
// ($prim f a b c ...)
336-
if tail {
337-
fmt.Fprintf(w, "__e.Return(")
338-
}
339-
340-
fmt.Fprintf(w, "%s(", primName)
341-
for i, arg := range args {
342-
if i != 0 {
343-
fmt.Fprintf(w, ", ")
344-
}
345-
if kl.IsSymbol(arg) {
346-
fmt.Fprintf(w, "%s", symbolAsVar(arg))
347-
} else {
348-
if err := cg.generateExpr(w, arg); err != nil {
349-
return true, err
350-
}
351-
}
352-
}
353-
fmt.Fprintf(w, ")")
354-
355-
if tail {
356-
fmt.Fprintf(w, ")\nreturn\n")
357-
}
358-
return true, nil
359-
}
360-
361-
var shenPrimitive = map[string]struct {
362-
Arity int
363-
Name string
364-
}{
365-
"get-time": {1, "PrimGetTime"},
366-
"close": {1, "PrimCloseStream"},
367-
"open": {2, "PrimOpenStream"},
368-
"read-byte": {1, "PrimReadByte"},
369-
"write-byte": {2, "PrimWriteByte"},
370-
"absvector?": {1, "PrimIsVector"},
371-
"<-address": {2, "PrimVectorGet"},
372-
"address->": {3, "PrimVectorSet"},
373-
"absvector": {1, "PrimAbsvector"},
374-
"str": {1, "PrimStr"},
375-
"<=": {2, "PrimLessEqual"},
376-
">=": {2, "PrimGreatEqual"},
377-
"<": {2, "PrimLessThan"},
378-
">": {2, "PrimGreatThan"},
379-
"error-to-string": {1, "PrimErrorToString"},
380-
"simple-error": {1, "PrimSimpleError"},
381-
"=": {2, "PrimEqual"},
382-
"-": {2, "PrimNumberSubtract"},
383-
"*": {2, "PrimNumberMultiply"},
384-
"/": {2, "PrimNumberDivide"},
385-
"+": {2, "PrimNumberAdd"},
386-
"string->n": {1, "PrimStringToNumber"},
387-
"n->string": {1, "PrimNumberToString"},
388-
"number?": {1, "PrimIsNumber"},
389-
"string?": {1, "PrimIsString"},
390-
"pos": {2, "PrimPos"},
391-
"tlstr": {1, "PrimTailString"},
392-
"cn": {2, "PrimStringConcat"},
393-
"intern": {1, "PrimIntern"},
394-
"hd": {1, "PrimHead"},
395-
"tl": {1, "PrimTail"},
396-
"cons": {2, "PrimCons"},
397-
"cons?": {1, "PrimIsPair"},
398-
"value": {1, "PrimValue"},
399-
"set": {2, "PrimSet"},
400-
"not": {1, "PrimNot"},
401-
"if": {3, "PrimIf"},
402-
"symbol?": {1, "PrimIsSymbol"},
403-
"read-file-as-bytelist": {1, "PrimReadFileAsByteList"},
404-
"read-file-as-string": {1, "PrimReadFileAsString"},
405-
"variable?": {1, "PrimIsVariable"},
406-
"integer?": {1, "PrimIsInteger"},
407-
}

0 commit comments

Comments
 (0)