Skip to content

Commit 3661f33

Browse files
committed
Create global memory which can be shared with the esp32.
1 parent 79ec40c commit 3661f33

File tree

6 files changed

+127
-16
lines changed

6 files changed

+127
-16
lines changed

README.md

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Copyright 2024 Blake Felt [email protected]
3131
# Contents
3232
* [Building ulp-forth](#building-ulp-forth)
3333
* [Using ulp-forth](#using-ulp-forth)
34+
* [Sharing memory](#sharing-memory)
35+
* [Threading models](#threading-models)
3436
* [Assembly words](#assembly-words)
3537
* [System words](#system-words)
3638
* [Clock words](#clock-words)
@@ -40,7 +42,7 @@ Copyright 2024 Blake Felt [email protected]
4042
* [Standard Core words](#standard-core-words)
4143
* [Standard Core Extension words](#standard-core-extension-words)
4244
* [Standard Double words](#standard-double-words)
43-
# [Optimizations](#optimizations)
45+
* [Optimizations](#optimizations)
4446

4547
# Building ulp-forth
4648

@@ -68,21 +70,65 @@ The cross compiler can be run with `ulp-forth build`. The user should pass in th
6870
* `--reserved` Number of reserved bytes for the ULP, for use with --assembly flag (default 8176). Note that the Espressif linker has a bug so has 12 less total bytes. Any space not used by code or data is used for the stacks.
6971
* `--subroutine` Use the subroutine threading model, see the [threading models](#threading-models) section.
7072

73+
74+
# Sharing memory
75+
76+
There are words that can be used to share memory with the esp32. When compiled with the `--custom_assembly` or `--assembly` flags, the output assembly will include the `.global` directive for the associated memory. This memory will not be optimized away.
77+
78+
| Shared word | Equivalent word |
79+
| ------------------ | --------------- |
80+
| `GLOBAL-VARIABLE` | `VARIABLE` |
81+
| `GLOBAL-2VARIABLE` | `2VARIABLE` |
82+
| `GLOBAL-ALLOCATE` | `ALLOCATE` |
83+
84+
Access should be done while holding the mutex, see the [System words](#system-words) section.
85+
86+
Example:
87+
88+
```forth
89+
global-variable example \ create a global variable named "example"
90+
91+
\ read an address while holding the mutex
92+
: global-@ ( address -- n )
93+
mutex.take \ take ownership of the mutex
94+
@ \ read the value at the address
95+
mutex.give \ release the mutex
96+
;
97+
98+
\ write to an address while holding the mutex
99+
: global-! ( n address -- )
100+
mutex.take \ take ownership of the mutex
101+
! \ write the value to the address
102+
mutex.give \ release the mutex
103+
;
104+
105+
\ get the value at "example"
106+
: get-example ( -- )
107+
example \ put the address of the memory onto the stack
108+
global-@ \ read it
109+
;
110+
111+
\ set the value at "example"
112+
: set-example ( n -- )
113+
example \ put the address of the memory onto the stack
114+
global-! \ write to it
115+
;
116+
```
117+
71118
# Threading models
72119

73-
There are two threading models for the output ULP code. This is forth definition of "threading" and is not the same as multithreading in other languages. It can be thought of as the execution environment.
120+
There are two threading models for the output ULP code. This is the forth definition of "threading" and is not the same as multithreading in other languages. It can be thought of as the execution environment.
74121

75-
Token threading is usually smaller and subroutine threading is usually bigger, but this can vary based on the program and optimizations.
122+
Token threading is usually smaller and subroutine threading is usually faster, but this can vary based on the program and optimizations.
76123

77124
## Token threading (default)
78-
79-
This is the default threading model. This uses a lightweight virtual machine to execute all forth words. This allows for some very compact code, but there is a speed penalty for the virtual machine.
125+
This uses a lightweight virtual machine to execute all forth words. This allows for some very compact code, but there is a speed penalty for the virtual machine.
80126

81127
Code using this is roughly 20% smaller than subroutine threaded code.
82128

83129
## Subroutine threading
84130

85-
This can be enabled with the `--subroutine` flag. It copmiles all forth words into assembly subroutines. This is very fast while executing, but there is a size penalty.
131+
This can be enabled with the `--subroutine` flag. It compiles all forth words into assembly subroutines. This is very fast while executing, but there is a size penalty.
86132

87133
Code using this is roughly 20% faster than token threaded code.
88134

pkg/forth/builtin/02_core.f

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,51 @@
1010
\ Immediate.
1111
: RECURSE ( -- ) LAST COMPILE, ; IMMEDIATE
1212

13+
: ALLOCATE ( n -- address ok )
14+
FALSE \ mark as not global
15+
[ 'A' WORD A ] LITERAL \ put an empty string as the name
16+
--ALLOCATE
17+
;
18+
19+
: GLOBAL-ALLOCATE ( n "\<spaces\>name" -- address ok )
20+
TRUE \ mark as global
21+
BL WORD \ parse the name
22+
--ALLOCATE
23+
;
24+
1325
\ Parse the next word delimited by a space. Allocate n cells. Create
1426
\ a definition for the word that places the address of the allocated
1527
\ memory onto the stack.
16-
: BUFFER: ( n -- )
28+
: BUFFER: ( n "\<spaces\>name" -- )
1729
ALLOCATE DROP \ allocate n words, drop the superfluous "ok" indicator but keep address
1830
: \ parse the next input, create a word with that name
1931
POSTPONE LITERAL \ compile the allocated address literal
2032
POSTPONE ; \ end the definition
2133
;
2234

23-
: VARIABLE 1 BUFFER: ;
35+
\ Parse the next word delimited by a space. Allocate n cells. Create
36+
\ a definition for the word that places the address of the allocated
37+
\ memory onto the stack. The allocated memory will be output to assembly
38+
\ with the same name and with the .global assembly tag.
39+
: GLOBAL-BUFFER: ( n "\<spaces\>name" -- )
40+
BL WORD \ ( n name ) get the name we want to create
41+
SWAP 1 PICK \ ( name n name ) copy the name
42+
TRUE SWAP \ ( name n true name ) prepare for allocation
43+
--ALLOCATE DROP SWAP \ ( address name ) allocate the memory, drop the "ok"
44+
--CREATE-FORTH \ ( address ) create the new forth word
45+
POSTPONE LITERAL \ ( ) compile the address
46+
POSTPONE EXIT \ compile an exit
47+
;
48+
49+
\ create a variable only visible to the ulp
50+
: VARIABLE ( "\<spaces\>name" -- )
51+
1 BUFFER:
52+
;
53+
54+
\ create a variable that can be shared with the esp32
55+
: GLOBAL-VARIABLE ( "\<spaces\>name" -- )
56+
1 GLOBAL-BUFFER:
57+
;
2458

2559
: DEFER
2660
1 ALLOCATE DROP \ allocate 1 word

pkg/forth/builtin/03_double.f

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,4 @@
7777
;
7878

7979
: 2VARIABLE 2 BUFFER: ;
80+
: GLOBAL-2VARIABLE 2 GLOBAL-BUFFER: ;

pkg/forth/flag.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ package forth
99

1010
// Flags associated with each word
1111
type Flag struct {
12-
Hidden bool // This word is hidden from being found.
13-
Immediate bool // This word should be executed immediately.
14-
Data bool // This Forth word is data and should not be optimized.
12+
Hidden bool // This word is hidden from being found.
13+
Immediate bool // This word should be executed immediately.
14+
Data bool // This Forth word is data and should not be optimized.
15+
GlobalData bool // This data word should be marked as .global in the output.
1516

1617
addedToList bool // This word is already added to the output list.
1718
recursive bool // This Forth word is recursive.

pkg/forth/primitive.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -599,9 +599,29 @@ func PrimitiveSetup(vm *VirtualMachine) error {
599599
},
600600
},
601601
{
602-
name: "ALLOCATE",
602+
name: "--ALLOCATE", // ( size global name -- address success )
603603
goFunc: func(vm *VirtualMachine, entry *DictionaryEntry) error {
604-
n, err := vm.Stack.PopNumber()
604+
// get name
605+
cell, err := vm.Stack.Pop()
606+
if err != nil {
607+
return JoinEntryError(err, entry, "could not pop name")
608+
}
609+
cellAddr, ok := cell.(CellAddress)
610+
if !ok {
611+
return EntryError(entry, "requires a name")
612+
}
613+
name, err := cellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
614+
if err != nil {
615+
return JoinEntryError(err, entry, "could not parse name")
616+
}
617+
// get global
618+
nGlobal, err := vm.Stack.PopNumber()
619+
if err != nil {
620+
return PopError(err, entry)
621+
}
622+
global := nGlobal != 0
623+
// get size
624+
n, err := vm.Stack.PopNumber() // the number of inputs
605625
if err != nil {
606626
return PopError(err, entry)
607627
}
@@ -614,14 +634,18 @@ func PrimitiveSetup(vm *VirtualMachine) error {
614634
w.Cells[i] = CellNumber{0}
615635
}
616636
de = DictionaryEntry{
637+
Name: string(name),
617638
Word: &w,
618-
Flag: Flag{Data: true},
639+
Flag: Flag{
640+
Data: true,
641+
GlobalData: global,
642+
},
619643
}
620-
cell := CellAddress{
644+
newCell := CellAddress{
621645
Offset: 0,
622646
Entry: &de,
623647
}
624-
err = vm.Stack.Push(cell)
648+
err = vm.Stack.Push(newCell)
625649
if err != nil {
626650
return PushError(err, entry)
627651
}

pkg/forth/ulpBuild.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ func (u *Ulp) buildDataWords() (string, error) {
250250
if err != nil {
251251
return "", err
252252
}
253+
if word.Entry.Flag.GlobalData {
254+
label := word.Entry.ulpName
255+
global := ".global " + label + "\r\n"
256+
asm = global + asm
257+
}
253258
output[i] = asm
254259
}
255260
return strings.Join(output, "\r\n"), nil

0 commit comments

Comments
 (0)