about summary refs log tree commit diff stats
path: root/transect/compiler9
blob: 26becf48a8b12fcf4099f29c438d7305e3b9297c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
=== Goal

A memory-safe language with a simple translator to x86 that can be feasibly
written without itself needing a translator.

Memory-safe: it should be impossible to:
  a) create a pointer out of arbitrary data, or
  b) to access heap memory after it's been freed.

Simple: do all the work in a 2-pass translator:
  Pass 1: check each instruction's types in isolation.
  Pass 2: emit code for each instruction in isolation.

=== Overview of the language

A program consists of a series of type, function and global variable declarations.
(Also constants and tests, but let's focus on these.)

Type declarations basically follow Hindley-Milner with product and (tagged) sum
types. Types are written in s-expression form. There's a `ref` type that's a
type-safe fat pointer, with an alloc id that gets incremented after each
allocation. Memory allocation and reclamation is manual. Dereferencing a ref
after its underlying memory is reclaimed (pointer alloc id no longer matches
payload alloc id) is guaranteed to immediately kill the program (like a
segfault).

  # product type
  type foo [
    x : int
    y : (ref int)
    z : bar
  ]

  # sum type
  choice bar [
    x : int
    y : point
  ]

Functions have a header and a series of instructions in the body:

  fn f a : int -> b : int [
    ...
  ]

Instructions have the following format:

  io1, io2, ... <- operation i1, i2, ...

i1, i2 operands on the right hand side are immutable. io1, io2 are in-out
operands. They're written to, and may also be read.

User-defined functions will be called with the same syntax. They'll translate
to a sequence of push instructions (one per operand, both in and in-out), a
call instruction, and a sequence of pop instructions, either to a black hole
(in operands) or a location (in-out operands). This follows the standard Unix
calling convention. Each operand needs to be something push/pop can accept.

Primitive operations depend on the underlying processor. We'd like each primitive
operation supported by the language to map to a single instruction in the ISA.
Sometimes we have to violate that (see below), but we definitely won't be
writing to any temporary locations behind the scenes. The language affords
control over registers, and tracking unused registers gets complex, and
besides we may have no unused registers at a specific point. Instructions only
modify their operands.

In most ISAs, instructions operate on at most a word of data at a time. They
also tend to not have more than 2-3 operands, and not modify more than 2
locations in memory.

Since the number of reads from memory is limited, we break up complex high-level
operations using a special type called `address`. Addresses are strictly
short-term entities. They can't be stored in a compound type, and they can't
be passed into or returned from a user-defined function. They also can't be
used after a function call (because it could free the underlying memory) or
label (because it gets complex to check control flow, and we want to translate
each instruction simply and in isolation).

=== Compilation to 32-bit x86

Values can be stored:
  in code (literals)
  in registers
  on the stack
  on the global segment

Variables on the stack are stored at *(ESP+n)
Global variables are stored at *disp32, where disp32 is statically known

Address variables have to be in a register.
  - You need them in a register to do a lookup, and
  - Saving them to even the stack increases the complexity of checks needed on
    function calls or labels.

Compilation proceeds by pattern matching over an instruction along with
knowledge about the types of its operands, as well as where they're stored
(register/stack/global). We now enumerate mappings for various categories of
instructions, based on the type and location of their operands.

Where types of operands aren't mentioned below, all operands of an instruction
should have the same (word-length) type.

Lots of special cases because of limitations of the x86 ISA. Beware.

A. x : int <- add y

  Requires y to be scalar. Result will always be an int. No pointer arithmetic.

  reg <- add literal    => 81 0/subop 3/mod                                                                                           ...(0)
  reg <- add reg        => 01 3/mod                                                                                                   ...(1)
  reg <- add stack      => 03 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 reg/r32                                        ...(2)
  reg <- add global     => 03 0/mod 5/rm32/include-disp32 global/disp32 reg/r32                                                       ...(3)
  stack <- add literal  => 81 0/subop 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 literal/imm32                          ...(4)
  stack <- add reg      => 01 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 reg/r32                                        ...(5)
  stack <- add stack    => disallowed
  stack <- add global   => disallowed
  global <- add literal => 81 0/subop 0/mod 5/rm32/include-disp32 global/disp32 literal/imm32                                         ...(6)
  global <- add reg     => 01 0/mod 5/rm32/include-disp32 global/disp32 reg/r32                                                       ...(7)
  global <- add stack   => disallowed
  global <- add global  => disallowed

Similarly for sub, and, or, xor and even copy. Replace the opcodes above with corresponding ones from this table:

                            add             sub           and           or            xor         copy/mov
  reg <- op literal         81 0/subop      81 5/subop    81 4/subop    81 1/subop    81 6/subop  c7
  reg <- op reg             01 or 03        29 or 2b      21 or 23      09 or 0b      31 or 33    89 or 8b
  reg <- op stack           03              2b            23            0b            33          8b
  reg <- op global          03              2b            23            0b            33          8b
  stack <- op literal       81 0/subop      81 5/subop    81 4/subop    81 1/subop    81 6/subop  c7
  stack <- op reg           01              29            21            09            31          89
  global <- op literal      81 0/subop      81 5/subop    81 4/subop    81 1/subop    81 6/subop  c7
  global <- op reg          01              29            21            09            31          89

B. x/reg : int <- mul y

  Requires both y to be scalar.
  x must be in a register. Multiplies can't write to memory.

  reg <- mul literal    => 69                                                                                                         ...(8)
  reg <- mul reg        => 0f af 3/mod                                                                                                ...(9)
  reg <- mul stack      => 0f af 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 reg/r32                                     ...(10)
  reg <- mul global     => 0f af 0/mod 5/rm32/include-disp32 global/disp32 reg/r32                                                    ...(11)

C. x/EAX/quotient : int, y/EDX/remainder : int <- idiv z     # divide EAX by z; store the result in EAX and EDX

  Requires source x and z to both be scalar.
  x must be in EAX and y must be in EDX. Divides can't write anywhere else.

  First clear EDX (we don't support ints larger than 32 bits):
  31/xor 3/mod 2/rm32/EDX 2/r32/EDX

  then:
  EAX, EDX <- idiv literal  => disallowed
  EAX, EDX <- idiv reg      => f7 7/subop 3/mod                                                                                       ...(12)
  EAX, EDX <- idiv stack    => f7 7/subop 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8                                    ...(13)
  EAX, EDX <- idiv global   => f7 7/subop 0/mod 5/rm32/include-disp32 global/disp32 reg/r32                                           ...(14)

D. x : int <- not

  Requires x to be an int.

  reg <- not                => f7 3/mod                                                                                               ...(15)
  stack <- not              => f7 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8                                            ...(16)
  global <- not             => f7 0/mod 5/rm32/include-disp32 global/disp32 reg/r32                                                   ...(17)

E. x : (address t) <- get o : T, %f

  (Assumes T.f has type t.)

  o can't be on a register since it's a non-primitive (likely larger than a word)
  f is a literal
  x must be in a register (by definition for an address)

  below '*' works on either address or ref types

  For raw stack values we want to read *(ESP+n)
  For raw global values we want to read *disp32
  For address stack values we want to read *(ESP+n)+
    *(ESP+n) contains an address
    so we want to compute *(ESP+n) + literal

  reg1 <- get reg2, literal       => 8d/lea 1/mod reg2/rm32 literal/disp8 reg1/r32                                                    ...(18)
  reg <- get stack, literal       => 8d/lea 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n+literal/disp8 reg/r32                  ...(19)
    (simplifying assumption: stack frames can't be larger than 256 bytes)
  reg <- get global, literal      => 8d/lea 0/mod 5/rm32/include-disp32 global+literal/disp32, reg/r32                                ...(20)

F. x : (offset T) <- index i : int, %size(T)

  reg1 <- index reg2, literal       => 69/mul 3/mod reg2/rm32 literal/imm32 -> reg1/r32
                                    or 68/mul 3/mod reg2/rm32 literal/imm8 -> reg1/r32                                                ...(21)
  reg1 <- index stack, literal      => 69/mul 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 literal/imm32 -> reg1/r32      ...(22)
  reg1 <- index global, literal     => 69/mul 0/mod 5/rm32/include-disp32 global/disp32 literal/imm32 -> reg1/r32                     ...(23)

  optimization: avoid multiply if literal is a power of 2
    use SIB byte if literal is 2, 4 or 8
    or left shift

G. x : (address T) <- advance o : (array T), idx : (offset T)

  reg <- advance a/reg, idx/reg   => 8d/lea 0/mod 4/rm32/SIB a/base idx/index 0/scale reg/r32                                         ...(24)
  reg <- advance stack, literal   => 8d/lea 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n+literal/disp8 reg/r32                  ...(25)
  reg <- advance stack, reg2      => 8d/lea 1/mod 4/rm32/SIB 4/base/ESP reg2/index 0/scale n/disp8 reg/r32                            ...(26)
  reg <- advance global, literal  => 8d/lea 0/mod 5/rm32/include-disp32 global+literal/disp32, reg/r32                                ...(27)

  also instructions for runtime bounds checking

=== Example

Putting it all together: code generation for `a[i].y = 4` where a is an array
of 2-d points with x, y coordinates.

If a is allocated on the stack, say of type (array point 6) at (ESP+4):

  offset/EAX : (offset point) <- index i, 8  # (22)
  tmp/EBX : (address point) <- advance a : (array point 6), offset/EAX  # (26)
  tmp2/ECX : (address number) <- get tmp/EBX : (address point), 4/y  # (18)
  *tmp2/ECX <- copy 4  # (5 for copy/mov with 0 disp8)

Many instructions, particularly variants of 'get' and 'advance' -- end up encoding the exact same instructions.
But the types differ, and the type-checker checks them differently.

=== Advanced checks

Couple of items require inserting mapping to multiple instructions:
  bounds checking against array length in 'advance'
  dereferencing 'ref' types (see type list up top)

A. Dereferencing a ref

    tmp/EDX <- advance *s, tmp0/EDI
      => compare (ESP+4), *(ESP+8)  ; '*' from compiler2
         jump-unless-equal panic
         EDX <- add ESP, 8
         EDX <- copy *EDX
         EDX <- add EDX, 4
         EDX <- 8d/lea EDX + result

=== More speculative ideas

Initialize data segment with special extensible syntax for literals. All
literals except numbers and strings start with %.

  %size(type) => compiler replaces with size of type
  %point(3, 4) => two words

and so on.

=== Credits

Forth
C
Rust
Lisp
qhasm
href='/danisanti/profani-tty/blame/src/muc.h?id=41b49cb5d6507636070c4d65f0e3f006c1ac9952'>^
a2726b6a ^
d25d6b45 ^
a2726b6a ^
17757c86 ^
a2726b6a ^

d25d6b45 ^
a2726b6a ^

f247f367 ^
a2726b6a ^

4b14c0c4 ^
a2726b6a ^

b50b786d ^
a2726b6a ^



41b49cb5 ^
a2726b6a ^

fac2b2cf ^
a2726b6a ^



84506cba ^
ba11e88d ^
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
  
        
                                 
  
                                                            













                                                                       
                                                                      
  











                                                                                

   

                  
 

                 
                               
                         

                         
 














                            





                                





                                    




                              


                                  
                 

           
                    
                     
 

                                                                                                             
 

                                              


                       
                                                                
 

                                           
 



                                                                               
 










                                                                                                                      
 
                                                                          
 




                                                                                            
 

                                                                                                                    
 
                                                  
                                   
                                                                                          

                                

                                                                         
                             
                         
                                                     
                                
                                                                                       
                             
                                                  
 

                                                                        
 

                                                                                   
 

                                                                                    
 

                                                                   
 



                                                                                
 

                                                                
 



                                                                                                      
 
      
/*
 * muc.h
 * vim: expandtab:ts=4:sts=4:sw=4
 *
 * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
 *
 * This file is part of Profanity.
 *
 * Profanity is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Profanity is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give permission to
 * link the code of portions of this program with the OpenSSL library under
 * certain conditions as described in each individual source file, and
 * distribute linked combinations including the two.
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception, you
 * may extend this exception to your version of the file(s), but you are not
 * obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version. If you delete this exception statement from all
 * source files in the program, then also delete it here.
 *
 */

#ifndef XMPP_MUC_H
#define XMPP_MUC_H

#include <glib.h>

#include "tools/autocomplete.h"
#include "ui/win_types.h"
#include "xmpp/contact.h"
#include "xmpp/jid.h"

typedef enum {
    MUC_ROLE_NONE,
    MUC_ROLE_VISITOR,
    MUC_ROLE_PARTICIPANT,
    MUC_ROLE_MODERATOR
} muc_role_t;

typedef enum {
    MUC_AFFILIATION_NONE,
    MUC_AFFILIATION_OUTCAST,
    MUC_AFFILIATION_MEMBER,
    MUC_AFFILIATION_ADMIN,
    MUC_AFFILIATION_OWNER
} muc_affiliation_t;

typedef enum {
    MUC_MEMBER_TYPE_UNKNOWN,
    MUC_MEMBER_TYPE_PUBLIC,
    MUC_MEMBER_TYPE_MEMBERS_ONLY
} muc_member_type_t;

typedef enum {
    MUC_ANONYMITY_TYPE_UNKNOWN,
    MUC_ANONYMITY_TYPE_NONANONYMOUS,
    MUC_ANONYMITY_TYPE_SEMIANONYMOUS
} muc_anonymity_type_t;

typedef struct _muc_occupant_t
{
    char* nick;
    gchar* nick_collate_key;
    char* jid;
    muc_role_t role;
    muc_affiliation_t affiliation;
    resource_presence_t presence;
    char* status;
} Occupant;

void muc_init(void);
void muc_close(void);

void muc_join(const char* const room, const char* const nick, const char* const password, gboolean autojoin);
void muc_leave(const char* const room);

gboolean muc_active(const char* const room);
gboolean muc_autojoin(const char* const room);

GList* muc_rooms(void);

void muc_set_features(const char* const room, GSList* features);

char* muc_nick(const char* const room);
char* muc_password(const char* const room);

void muc_nick_change_start(const char* const room, const char* const new_nick);
void muc_nick_change_complete(const char* const room, const char* const nick);
gboolean muc_nick_change_pending(const char* const room);
char* muc_old_nick(const char* const room, const char* const new_nick);

gboolean muc_roster_contains_nick(const char* const room, const char* const nick);
gboolean muc_roster_complete(const char* const room);
gboolean muc_roster_add(const char* const room, const char* const nick, const char* const jid, const char* const role,
                        const char* const affiliation, const char* const show, const char* const status);
void muc_roster_remove(const char* const room, const char* const nick);
void muc_roster_set_complete(const char* const room);
GList* muc_roster(const char* const room);
Autocomplete muc_roster_ac(const char* const room);
Autocomplete muc_roster_jid_ac(const char* const room);
void muc_jid_autocomplete_reset(const char* const room);
void muc_jid_autocomplete_add_all(const char* const room, GSList* jids);

Occupant* muc_roster_item(const char* const room, const char* const nick);

gboolean muc_occupant_available(Occupant* occupant);
const char* muc_occupant_affiliation_str(Occupant* occupant);
const char* muc_occupant_role_str(Occupant* occupant);
GSList* muc_occupants_by_role(const char* const room, muc_role_t role);
GSList* muc_occupants_by_affiliation(const char* const room, muc_affiliation_t affiliation);

void muc_occupant_nick_change_start(const char* const room, const char* const new_nick, const char* const old_nick);
char* muc_roster_nick_change_complete(const char* const room, const char* const nick);

void muc_confserver_add(const char* const server);
void muc_confserver_reset_ac(void);
char* muc_confserver_find(const char* const search_str, gboolean previous, void* context);
void muc_confserver_clear(void);

void muc_invites_add(const char* const room, const char* const password);
void muc_invites_remove(const char* const room);
gint muc_invites_count(void);
GList* muc_invites(void);
gboolean muc_invites_contain(const char* const room);
void muc_invites_reset_ac(void);
char* muc_invites_find(const char* const search_str, gboolean previous, void* context);
void muc_invites_clear(void);
char* muc_invite_password(const char* const room);

void muc_set_subject(const char* const room, const char* const subject);
char* muc_subject(const char* const room);

void muc_pending_broadcasts_add(const char* const room, const char* const message);
GList* muc_pending_broadcasts(const char* const room);

char* muc_autocomplete(ProfWin* window, const char* const input, gboolean previous);
void muc_autocomplete_reset(const char* const room);

gboolean muc_requires_config(const char* const room);
void muc_set_requires_config(const char* const room, gboolean val);

void muc_set_role(const char* const room, const char* const role);
void muc_set_affiliation(const char* const room, const char* const affiliation);
char* muc_role_str(const char* const room);
char* muc_affiliation_str(const char* const room);

muc_member_type_t muc_member_type(const char* const room);
muc_anonymity_type_t muc_anonymity_type(const char* const room);

GList* muc_members(const char* const room);
void muc_members_add(const char* const room, const char* const jid);
void muc_members_remove(const char* const room, const char* const jid);
void muc_members_update(const char* const room, const char* const jid, const char* const affiliation);

#endif