What does 'target-only primative' mean?

Repo here: GitHub - jcarvajal288/asteroids

I’m participating in the Lisp Game Jam and basing my code off of the Breakout clone example. I’m trying to extract the draw code out of game.scm and into another module draw.scm in the modules folder. When I run make serve I get the error ‘target-only primitive’ from hoot/primitives.scm:392:2. The function at that location has this as a comment:

“Emit inline WebAssembly code. @var{code} is a WebAssembly module
expressed in WebAssembly’s s-expression syntax. The backend expects the
parsed module to contain a single function. The arguments
@var{arg}… should correspond to the parameters of the function. The
number of result values is also determined from the function signature.”

What does this mean? I see other modules that have multiple functions importing just fine, like (math vector).

“Target-only primitive” means that something that only works in wasm was called during macro-expansion time on the Guile host. In practice, as you’ve discovered, it means that inline-wasm was called. This happens when a top-level definition calls something in hoot’s standard library that is implemented using inline-wasm. I took a look at your code and I see the culprit: (define image:ship (make-image "assets/images/ship-1.png")) is calling make-image at the top-level. This expression will be evaluated at macro expansion time. make-image is an FFI binding to a browser DOM method, which makes it unusable at compile time. The solution is to defer loading assets until runtime using a procedure that will call make-image. Hope this helps!

Given what you said, I’m a bit confused why calling make-image at the top level in game.scm works. The breakout example loads all its assets this way. Is there something different about calling this line in the main file vs in a module?

Just to check if I’m on the right track, should I make some kind of empty variable at the top level of draw.scm that I then populate in a load-assets procedure or similar?

Yes, the main program is treated differently than modules. The reason being that the main program is a kind of leaf node in the module graph that nothing else can depend on. Andy Wingo explains the process Hoot uses in this blog post. Notable excerpt:

For those declarative languages with macros, Scheme included, I understand the state of the art is to expand module-by-module and then stitch together the results of expansion later, using a kind of link-time optimization. You visit a module’s definitions twice: once to evaluate them while expanding, resulting in live definitions that can be used by further syntax expanders, and once to residualize an abstract syntax tree, which will eventually be spliced into the compilation unit.

Procedural macros, which can evaluate arbitrary code at compile-time, make whole-program compilation quite tricky!

And yes, one way to solve your problem is to make a procedure that will set! some top-level variables to initialize them at the appropriate time. There are many other ways you could do this, but this way is simple and likely requires changing the fewest lines of code.