Skip to content

Commit c48eec5

Browse files
authored
Fix AOT namespace dependencies and dynamic var compilation (#101)
- Add explicit loading of namespaces required by AOT-compiled core (protocols, string, go.io) - Fixes UnboundVar errors caused by missing transitive dependencies - TODO: AOT should automatically identify and load these dependencies - Fix dynamic var generation to capture root binding using fresh goroutine - Ensures vars are initialized with root values, not compile-time dynamic bindings - Add AOT compilation for additional stdlib namespaces (go.io, go.types, uuid) - Set GLOJURE_USE_AOT=false during AOT generation to avoid circular dependencies - Skip non-var namespace mappings during codegen instead of panicking - Clean up leftover generated loader files (core_deftype, core_print) from AOT development - Consolidate AOT loader imports in pkg/glj/init.go These changes resolve runtime errors when using AOT-compiled namespaces by ensuring all required dependencies are loaded.
1 parent 095b1af commit c48eec5

File tree

17 files changed

+3756
-114574
lines changed

17 files changed

+3756
-114574
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ doc/repl/glj.wasm
33
.direnv
44

55
# useful to symlink in for context
6-
clojure
6+
/clojure

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ gocmd:
5252
.PHONY: generate
5353
generate:
5454
@go generate ./...
55-
@echo "(map compile '[glojure.core glojure.core.async glojure.walk glojure.template])" | \
56-
GLOJURE_STDLIB_PATH=./pkg/stdlib $(GO_CMD) run ./cmd/glj
55+
@echo "(map compile '[glojure.core glojure.go.io glojure.core.async glojure.walk glojure.template glojure.go.types glojure.uuid])" | \
56+
GLOJURE_USE_AOT=false GLOJURE_STDLIB_PATH=./pkg/stdlib $(GO_CMD) run ./cmd/glj
5757

5858
.PHONY: build
5959
build: $(GLJ)

pkg/glj/init.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import (
88
_ "github.com/glojurelang/glojure/pkg/gen/gljimports"
99
"github.com/glojurelang/glojure/pkg/lang"
1010

11+
// Add NS loaders for the standard library.
12+
_ "github.com/glojurelang/glojure/pkg/stdlib/glojure/core"
13+
_ "github.com/glojurelang/glojure/pkg/stdlib/glojure/core/async"
14+
_ "github.com/glojurelang/glojure/pkg/stdlib/glojure/go/io"
15+
_ "github.com/glojurelang/glojure/pkg/stdlib/glojure/protocols"
16+
1117
"github.com/glojurelang/glojure/pkg/runtime"
1218
)
1319

pkg/gljmain/gljmain.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import (
1010
// bootstrap the runtime
1111
_ "github.com/glojurelang/glojure/pkg/glj"
1212

13-
_ "github.com/glojurelang/glojure/pkg/stdlib/glojure/core"
14-
_ "github.com/glojurelang/glojure/pkg/stdlib/glojure/core/async"
15-
_ "github.com/glojurelang/glojure/pkg/stdlib/glojure/protocols"
16-
1713
"github.com/glojurelang/glojure/pkg/lang"
1814
"github.com/glojurelang/glojure/pkg/reader"
1915
"github.com/glojurelang/glojure/pkg/repl"

pkg/runtime/codegen.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ func (g *Generator) Generate(ns *lang.Namespace) error {
144144
second, _ := lang.Nth(entry, 1)
145145
vr, ok := second.(*lang.Var)
146146
if !ok {
147-
panic(fmt.Sprintf("expected var, got %T", second))
147+
continue // skip non-var mappings
148+
// TODO: handle non-var mappings like direct references to functions or values
149+
// panic(fmt.Sprintf("can't codegen %v: expected var, got %T (%v)", name, second, second))
148150
}
149151

150152
if !(vr.Namespace() == ns && lang.Equals(vr.Symbol(), name)) {
@@ -402,7 +404,14 @@ func (g *Generator) generateVar(nsVariableName string, name *lang.Symbol, vr *la
402404

403405
// check if the var has a value
404406
if vr.IsBound() {
405-
g.writef("%s = %s.InternWithValue(%s, %s, true)\n", varVar, nsVariableName, varSym, g.generateValue(vr.Get()))
407+
// we call Get() on a new goroutine to ensure we get the root value in the case
408+
// of dynamic vars
409+
valChan := make(chan any)
410+
go func() {
411+
valChan <- vr.Get()
412+
}()
413+
v := <-valChan
414+
g.writef("%s = %s.InternWithValue(%s, %s, true)\n", varVar, nsVariableName, varSym, g.generateValue(v))
406415
} else {
407416
g.writef("%s = %s.Intern(%s)\n", varVar, nsVariableName, varSym)
408417
}

pkg/runtime/envinit.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ func NewEnvironment(opts ...EvalOption) lang.Environment {
117117
// Add stdlib
118118
RT.Load("glojure/core")
119119

120+
// Workaround to ensure namespaces that are required by core are loaded.
121+
// TODO: AOT should identify this dependency and generate code to load it.
122+
if useAot {
123+
RT.Load("glojure/core/protocols")
124+
RT.Load("glojure/string")
125+
RT.Load("glojure/go/io")
126+
}
127+
120128
// Set the glojure version
121129
core := lang.FindNamespace(lang.NewSymbol("glojure.core"))
122130
versionVar := core.FindInternedVar(lang.NewSymbol("*glojure-version*"))

pkg/runtime/evalast.go

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,9 @@ func (env *environment) EvalASTTheVar(n *ast.Node) (interface{}, error) {
204204
return n.Sub.(*ast.TheVarNode).Var, nil
205205
}
206206

207-
// TODO: this is a bit of a mess
207+
// TODO: fix up, as this is a bit of a mess. This is a compatibility
208+
// layer to make certain uses of Clojure Compiler methods work in
209+
// core.
208210
type evalCompiler struct{}
209211

210212
var (
@@ -261,22 +263,9 @@ func (env *environment) EvalASTMaybeClass(n *ast.Node) (interface{}, error) {
261263
func (env *environment) EvalASTMaybeHostForm(n *ast.Node) (interface{}, error) {
262264
hostFormNode := n.Sub.(*ast.MaybeHostFormNode)
263265
field := hostFormNode.Field
264-
// TODO: implement this for real
265-
switch hostFormNode.Class {
266-
case "glojure.lang.PersistentTreeSet":
267-
switch field.Name() {
268-
case "create":
269-
return func(keys interface{}) interface{} {
270-
var ks []interface{}
271-
for seq := lang.Seq(keys); seq != nil; seq = seq.Next() {
272-
ks = append(ks, seq.First())
273-
}
274-
return lang.NewSet(ks...)
275-
}, nil
276-
}
277-
}
278266

279-
// TODO: how to handle?
267+
// TODO: should we support any version of this? Go doesn't have any notion of static
268+
// vs instance fields.
280269
panic("EvalASTMaybeHostForm: " + hostFormNode.Class + "/" + field.Name())
281270
}
282271

pkg/runtime/rtcompat.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,13 @@ func compileNSToFile(fs fs.FS, scriptBase string) {
212212
var buf bytes.Buffer
213213
gen := NewGenerator(&buf)
214214
currNS := VarCurrentNS.Deref().(*lang.Namespace)
215+
// only generate if the namespace has a name that matches the scriptBase
216+
nsName := strings.ReplaceAll(scriptBase, string(os.PathSeparator), ".")
217+
if currNS.Name().Name() != nsName {
218+
fmt.Printf("Skipping code generation for namespace %s: does not match script base %s\n", currNS.Name(), nsName)
219+
return
220+
}
221+
215222
if err = gen.Generate(currNS); err != nil {
216223
panic(fmt.Errorf("failed to generate code for namespace %s: %w", currNS.Name(), err))
217224
}

pkg/stdlib/glojure/core/async/loader.go

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/stdlib/glojure/core/loader.go

Lines changed: 18 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)