1
0
forked from Mirror/wren

Revise embedding docs a bit.

This commit is contained in:
Bob Nystrom
2016-08-26 07:11:50 -07:00
parent a8cf1c9d71
commit 0a4f1b370c
3 changed files with 118 additions and 65 deletions

View File

@ -1,17 +1,28 @@
^title Calling Wren from C
From C, we can tell Wren to do stuff by calling `wrenInterpret()`, but that's not always the ideal way to drive the VM. First of all, it's slow. It has to parse and compile the string of source code you give it. Wren has a pretty fast compiler, but that's still a good bit of work.
From C, we can tell Wren to do stuff by calling `wrenInterpret()`, but that's
not always the ideal way to drive the VM. First of all, it's slow. It has to
parse and compile the string of source code you give it. Wren has a pretty fast
compiler, but that's still a good bit of work.
It's also not an effective way to communicate. You can't pass arguments to Wren—at least, not without doing something nasty like converting them to literals in a string of source code—and we can get a result value back.
It's also not an effective way to communicate. You can't pass arguments to
Wren—at least, not without doing something nasty like converting them to
literals in a string of source code—and we can't get a result value back.
`wrenInterpret()` is great for loading some new code into the VM. But it's not the best way to execute code that's already been loaded. What we want to do is invoke some already compiled chunk of code. Since Wren is an object-oriented language, "chunk of code" means a [method][], not a [function][].
`wrenInterpret()` is great for loading code into the VM, but it's not the best
way to execute code that's already been loaded. What we want to do is invoke
some already compiled chunk of code. Since Wren is an object-oriented language,
"chunk of code" means a [method][], not a [function][].
[method]: ../method-calls.html
[function]: ../functions.html
The C API for doing this is `wrenCall()`. In order to invoke a Wren method from C, first we need a way to refer to the method itself.
The C API for doing this is `wrenCall()`. In order to invoke a Wren method from
C, first we need a few things:
* **The method to call.** Wren is dynamically typed, so this means we'll look it up by name. Further, since Wren supports overloading by arity, we actually need its entire [signature][].
* **The method to call.** Wren is dynamically typed, so this means we'll look it
up by name. Further, since Wren supports overloading by arity, we actually
need its entire [signature][].
[signature]: ../method-calls.html#signature
@ -29,20 +40,34 @@ When you run a chunk of Wren code like this:
:::wren
object.someMethod(1, 2, 3)
At runtime, the VM has to look up the class of `object` and find a method there whose signature is `someMethod(_,_,_)`. This sounds like it's doing some string manipulation—at the very least hashing the signature—every time a method is called. That's how many dynamic languages work.
At runtime, the VM has to look up the class of `object` and find a method there
whose signature is `someMethod(_,_,_)`. This sounds like it's doing some string
manipulation—at the very least hashing the signature—every time a
method is called. That's how many dynamic languages work.
But, as you can imagine, that's pretty slow. So, instead, Wren does as much of that work at compile time as it can. When it's compiling the above code to bytecode, it takes that method signature a converts it to a *method symbol*, a number that uniquely identifes that method. That's the only part of the process that requires treating a signature as a string.
But, as you can imagine, that's pretty slow. So, instead, Wren does as much of
that work at compile time as it can. When it's compiling the above code to
bytecode, it takes that method signature a converts it to a *method symbol*, a
number that uniquely identifes that method. That's the only part of the process
that requires treating a signature as a string.
At runtime, the VM just looks for the method *symbol* in the receiver's class's method table. In fact, the way it's implemented today, the symbol is simply the array index into the table. That's why method calls are so fast in Wren.
At runtime, the VM just looks for the method *symbol* in the receiver's class's
method table. In fact, the way it's implemented today, the symbol is simply the
array index into the table. That's why method calls are so fast in Wren.
It would be a shame if calling a method from C didn't have that same speed benefit. So to do that, we split the process of calling a method into two steps.
It would be a shame if calling a method from C didn't have that same speed
benefit. So to do that, we split the process of calling a method into two steps.
First, we create a handle that represents a "compiled" method signature. You do that using this:
First, we create a handle that represents a "compiled" method signature. You do
that using this:
:::c
WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
That takes a method signature as a string and gives you back an opaque handle that represents the compiled method symbol. Now you have a *reusable* handle that can be used to very quickly call a certain method given a receiver and some arguments.
That takes a method signature as a string and gives you back an opaque handle
that represents the compiled method symbol. Now you have a *reusable* handle
that can be used to very quickly call a certain method given a receiver and some
arguments.
This is just a regular WrenHandle, which means you can hold onto it as long as
you like. Typically, you'd call this once outside of your applications
@ -52,15 +77,26 @@ to you to release it when you no longer need it by calling
### Setting up a receiver
OK, we have a method, but who are we calling it on? We need a receiver, and as you can probably guess after reading the [last section][], we give that to Wren by storing it in a slot. In particular, **the receiver for a method call goes in slot zero.**
OK, we have a method, but who are we calling it on? We need a receiver, and as
you can probably guess after reading the [last section][], we give that to Wren
by storing it in a slot. In particular, **the receiver for a method call goes in
slot zero.**
Any object you store in that slot can be used as a receiver. You could even call `+` on a number by storing a number in there if you felt like it.
Any object you store in that slot can be used as a receiver. You could even call
`+` on a number by storing a number in there if you felt like it.
[last section]: slots-and-handles.html
*Needing* to pick some kind of receiver from C might feel strange. C is procedural, so it's natural to want to just invoke a bare *function* from Wren, but Wren isn't procedural. Instead, if you want to define some executable operation that isn't logically tied to a specific object, the natural way is to define a static method on an appropriate class.
*Needing* to pick some kind of receiver from C might feel strange. C is
procedural, so it's natural to want to just invoke a bare *function* from Wren,
but Wren isn't procedural. Instead, if you want to define some executable
operation that isn't logically tied to a specific object, the natural way is to
define a static method on an appropriate class.
For example, say we're making a game engine. From C, we want to tell the game engine to update all of the entities each frame. We'll keep track of the list of entities within Wren, so from C, there's no obvious object to call `update(_)` on. Instead, we'll just make it a static method:
For example, say we're making a game engine. From C, we want to tell the game
engine to update all of the entities each frame. We'll keep track of the list of
entities within Wren, so from C, there's no obvious object to call `update(_)`
on. Instead, we'll just make it a static method:
:::wren
class GameEngine {
@ -69,20 +105,25 @@ For example, say we're making a game engine. From C, we want to tell the game en
}
}
So, very often, when you call a method from C, you'll be calling a static method. But, even then, you need a receiver. Now, though, the receiver is the *class itself*. Classes are first class objects in Wren, and when you define a named class, you're really declaring a variable with the class's name and storing a reference to the class object in it.
So, very often, when you call a method from C, you'll be calling a static
method. But, even then, you need a receiver. Now, though, the receiver is the
*class itself*. Classes are first class objects in Wren, and when you define a
named class, you're really declaring a variable with the class's name and
storing a reference to the class object in it.
Assuming you declared that class at the top level, the C API [gives you a way to look it up][variable].
Assuming you declared that class at the top level, the C API [gives you a way to
look it up][variable]. We can get a handle to the above class like so:
[variable]: slots-and-handles.html#looking-up-variables
We can get a handle to the above class like so:
:::c
// Load the class into slot 0.
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "main", "GameEngine", 0);
We could do this every time we call `update()`, but, again, that's kind of slow because we're looking up "GameEngine" by name each time. A faster solution is to create a handle to the class once and use it each time:
We could do this every time we call `update()`, but, again, that's kind of slow
because we're looking up "GameEngine" by name each time. A faster solution is to
create a handle to the class once and use it each time:
:::c
// Load the class into slot 0.
@ -90,40 +131,57 @@ We could do this every time we call `update()`, but, again, that's kind of slow
wrenGetVariable(vm, "main", "GameEngine", 0);
WrenHandle* gameEngine = wrenGetSlotHandle(vm, 0);
Now, each time we want to call a method on GameEngine, we store that value back in slot zero:
Now, each time we want to call a method on GameEngine, we store that value back
in slot zero:
:::c
wrenSetSlotHandle(vm, 0, gameEngine);
Just like we hoisted `wrenMakeCallHandle()` out of our performance critical loop, we can hoist the call to `wrenGetVariable()` out. Of course, if your code isn't performance critical, you don't have to do this.
Just like we hoisted `wrenMakeCallHandle()` out of our performance critical
loop, we can hoist the call to `wrenGetVariable()` out. Of course, if your code
isn't performance critical, you don't have to do this.
### Passing arguments
We've got a receiver in slot zero now, next we need to pass in any other arguments. In our GameEngine example, that's just the elapsed time. The remaining arguments go in adjacent slots, right after the receiver. So our one elapsed time goes into slot one. You can use any of the slot functions to set this up. For the example, it's just:
We've got a receiver in slot zero now, next we need to pass in any other
arguments. In our GameEngine example, that's just the elapsed time. The
remaining arguments go in adjacent slots, right after the receiver. So our one
elapsed time goes into slot one. You can use any of the slot functions to set
this up. For the example, it's just:
:::c
wrenSetSlotDouble(vm, 1, elapsedTime);
### Calling the method
We have all of the data in place, so all that's left is to pull the trigger and tell the VM to start running some code. There's one more function to call:
We have all of the data in place, so all that's left is to pull the trigger and
tell the VM to start running some code. There's one more function to call:
:::c
WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method);
It takes the method handle we created using `wrenMakeCallHandle()`. It assumes you have already set up the receiver and arguments in the slot array. Critically, it assumes you have as many arguments as the method signature defines. If you call a method like `takeThree(_,_,_)` and don't put three arguments in the slot array, bad things we'll happen.
It takes the method handle we created using `wrenMakeCallHandle()`. It assumes
you have already set up the receiver and arguments in the slot array.
Critically, it assumes you have as many arguments as the method signature
defines. If you call a method like `takeThree(_,_,_)` and don't put three
arguments in the slot array, bad things will happen.
Now Wren starts running code. It looks up the method on the receiver, executes it and keeps running until either the method returns or a fiber [suspends][].
Now Wren starts running code. It looks up the method on the receiver, executes
it and keeps running until either the method returns or a fiber [suspends][].
[suspend]: ../modules/core/fiber.html#fiber.suspend()
[suspends]: ../modules/core/fiber.html#fiber.suspend()
`wrenCall()` returns the same WrenInterpretResult enum to tell you if the method completed successfully or a runtime error occurred. You'll never get a compile error from `wrenCall()`.
`wrenCall()` returns the same WrenInterpretResult enum to tell you if the method
completed successfully or a runtime error occurred.
### Getting the return value
When `wrenCall()` returns, it leaves the slot array in place. In slot zero, you can find the method's return value, which you can access using any of the slot reading functions. If you don't need the return value, you can ignore it.
When `wrenCall()` returns, it leaves the slot array in place. In slot zero, you
can find the method's return value, which you can access using any of the slot
reading functions. If you don't need the return value, you can ignore it.
This is how you drive Wren from C, but how do you put control in Wren's hands? For that, you'll need the next section...
This is how you drive Wren from C, but how do you put control in Wren's hands?
For that, you'll need the next section...
<a class="right" href="calling-c-from-wren.html">Calling C From Wren &rarr;</a>
<a href="slots-and-handles.html">&larr; Slots and Handles</a>

View File

@ -7,20 +7,19 @@ features. Designing this API well requires satisfying several constraints:
1. **Wren is dynamically typed, but C is not.** A variable can hold a value of
any type in Wren, but that's definitely not the case in C unless you define
some sort of variant type, which ultimately just kicks the problem down the
road. Eventually, we have to move data across the boundary between typed and
untyped code.
road. Eventually, we have to move data across the boundary between statically and dynamically typed code.
2. **Wren uses garbage collection, but C manages memory manually.** GC adds a
few constraints on the API. The VM must be able to find every Wren object
that is still usable, even if that object is being held onto by native C
code. Otherwise, it could free an object that's still in use.
that is still usable, even if that object is being referenced from native C
code. Otherwise, Wren could free an object that's still in use.
Also, we ideally don't want to let native C code see a bare pointer to a
chunk of memory managed by Wren. Many garbage collection strategies involve
[moving objects][] in memory. If we allow C code to point directly to an
object, that pointer will be left dangling when the object moves. To prevent
that, we can't using moving GCs even though they have some nice performance
properties.
object, that pointer will be left dangling when the object moves. Wren's GC
doesn't move objects today, but we would like to keep that option for the
future.
3. **The embedding API needs to be fast.** Users may add layers of abstraction
on top of the API to make it more pleasant to work with, but the base API
@ -29,14 +28,14 @@ features. Designing this API well requires satisfying several constraints:
it's too slow. There is no lower level alternative.
4. **We want the API to be pleasant to use.** This is the last constraint
because it's the softest. Of course, we want a beautiful, usable, API. But we
because it's the softest. Of course, we want a beautiful, usable API. But we
really *need* to handle the above, so we're willing to make things a bit more
of a chore to reach the first three goals.
[moving objects]: https://en.wikipedia.org/wiki/Tracing_garbage_collection#Copying_vs._mark-and-sweep_vs._mark-and-don.27t-sweep
Fortunately, Wren isn't the first language to tackle this. If you're familiar
with [Lua's C API][lua], you'll find Wren's to be fairly familiar.
Fortunately, we aren't the first people to tackle this. If you're familiar with
[Lua's C API][lua], you'll find Wren's similar.
[lua]: https://www.lua.org/pil/24.html
@ -133,24 +132,18 @@ You can tell the VM to execute a string of Wren source code like so:
WrenInterpretResult result = wrenInterpret(vm,
"System.print(\"I am running in a VM!\")");
The string is a chunk of Wren code to execute&mdash;a series of one or more
statements separated by newlines. Wren copies the string, so you can free it
after calling this.
When you call `wrenInterpret()`, Wren first compiles your source to bytecode. If
an error occurs, it returns immediately with `WREN_RESULT_COMPILE_ERROR`.
The string is a series of one or more statements separated by newlines. Wren
copies the string, so you can free it after calling this. When you call
`wrenInterpret()`, Wren first compiles your source to bytecode. If an error
occurs, it returns immediately with `WREN_RESULT_COMPILE_ERROR`.
Otherwise, Wren spins up a new [fiber][] and executes the code in that. Your
code can in turn spawn whatever other fibers it wants. It keeps running fibers
until they all complete or the current one [suspends].
until they all complete or the one [suspends].
[fiber]: ../concurrency.html
[suspends]: ../modules/core/fiber.html#fiber.suspend()
The code runs in a special "main" module. Everytime you call `wrenInterpret()`,
it runs in the same module. That way, top-level names defined in one call can be
accessed in later ones.
If a [runtime error][] occurs (and another fiber doesn't handle it), Wren abort
fibers all the way back to the main one and returns `WREN_RESULT_RUNTIME_ERROR`.
Otherwise, when the last fiber successfully returns, it returns
@ -158,6 +151,10 @@ Otherwise, when the last fiber successfully returns, it returns
[runtime error]: error-handling.html
All code passed to `wrenInterpret()` runs in a special "main" module. That way,
top-level names defined in one call can be accessed in later ones. It's similar
to a REPL session.
## Shutting down a VM
Once the party is over and you're ready to end your relationship with a VM, you

View File

@ -2,8 +2,8 @@
With `wrenInterpret()`, we can execute code, but that code can't do anything
particularly interesting. Out of the box, the VM is very isolated from the rest
of the world, so pretty much all it can do is burn CPU cycles. It can turn your
laptop into a lap warmer, but that's about it.
of the world, so pretty much all it can do is turn your laptop into a lap
warmer.
To make our Wren code *useful*, the VM needs to communicate with the outside
world. Wren uses a single unified set of functions for passing data into and out
@ -19,7 +19,7 @@ can leave bits of data on for the other side to process.
The array is zero-based, and each slot holds a value of any type. It is
dynamically sized, but it's your responsibility to ensure there are enough
slots before you start using them. You do this by calling:
slots *before* you use them. You do this by calling:
:::c
wrenEnsureSlots(WrenVM* vm, int slotCount);
@ -32,11 +32,6 @@ before populating the slots with data that you want to send to Wren.
wrenEnsureSlots(vm, 4);
// Can now use slots 0 through 3, inclusive.
When Wren is [calling your C code][] and passing data to you, it ensures there
are enough slots for the objects it is sending you.
[calling your c code]: calling-c-from-wren.html
After you ensure an array of slots, you can only rely on them being there until
you pass control back to Wren. That includes calling `wrenCall()` or
`wrenInterpret()`, or returning from a [foreign method][].
@ -56,6 +51,11 @@ It returns the number of slots in the array. Note that this may be higher than
the size you've ensured. Wren reuses the memory for this array when possible,
so you may get one bigger than you need if it happened to be laying around.
When Wren [calls your C code][] and passes data to you, it ensures there are
enough slots for the objects it is sending you.
[calls your c code]: calling-c-from-wren.html
### Writing slots
Once you have some slots, you can store data in them using a number of functions all named `wrenSetSlot<type>()` where `<type>` is the kind of data.
@ -143,11 +143,9 @@ Here's the first:
const char* name, int slot);
This looks up a top level variable with the given name in the module with the
given name and stores its value in the given slot. (Code you executed using
`wrenInterpret()` implicitly goes into a module named "main".)
Note that classes are just objects stored in variables too, so you can use this
to look up a class by its name. Handy for calling static methods on it.
given name and stores its value in the given slot. Note that classes are just
objects stored in variables too, so you can use this to look up a class by its
name. Handy for calling static methods on it.
Like any method that works with strings, this one is a bit slow. It has to
hash the name and look it up in the module's string table. You might want to