Putting C/C++ on a Macro Diet (as in eat more macros, not abstain - - PowerPoint PPT Presentation

putting c c on a macro diet
SMART_READER_LITE
LIVE PREVIEW

Putting C/C++ on a Macro Diet (as in eat more macros, not abstain - - PowerPoint PPT Presentation

Putting C/C++ on a Macro Diet (as in eat more macros, not abstain from macros) Matvey Soloviev (Cornell University) Graduate Seminar 1 Sliding Scale of Expressivity Expressivity: What programs can you express? How hard is it to express a given


slide-1
SLIDE 1

Putting C/C++ on a Macro Diet

(as in eat more macros, not abstain from macros)

Matvey Soloviev (Cornell University) Graduate Seminar

1

slide-2
SLIDE 2

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-3
SLIDE 3

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-4
SLIDE 4

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-5
SLIDE 5

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-6
SLIDE 6

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-7
SLIDE 7

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-8
SLIDE 8

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-9
SLIDE 9

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-10
SLIDE 10

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-11
SLIDE 11

Sliding Scale of Expressivity

Expressivity: What programs can you express? How hard is it to express a given program? Two dimensions, but let’s project down to one.

Only one program Calvinball LISP C/C++ Rust Golang

General trend away from expressivity?

2

slide-12
SLIDE 12

Types of expressivity

Express more programs. Express given program in more ways. ← you are here Why care about more ways to express a program if we already have one? Want shorter code. Want easier to read code. Want code that is closer to your mental model.

3

slide-13
SLIDE 13

Types of expressivity

Express more programs. Express given program in more ways. ← you are here Why care about more ways to express a program if we already have one? Want shorter code. Want easier to read code. Want code that is closer to your mental model.

3

slide-14
SLIDE 14

Types of expressivity

Express more programs. Express given program in more ways. ← you are here Why care about more ways to express a program if we already have one? Want shorter code. Want easier to read code. Want code that is closer to your mental model.

3

slide-15
SLIDE 15

Types of expressivity

Express more programs. Express given program in more ways. ← you are here Why care about more ways to express a program if we already have one? Want shorter code. Want easier to read code. Want code that is closer to your mental model.

3

slide-16
SLIDE 16

Types of expressivity

Express more programs. Express given program in more ways. ← you are here Why care about more ways to express a program if we already have one? Want shorter code. Want easier to read code. Want code that is closer to your mental model.

3

slide-17
SLIDE 17

How are languages made expressive?

Control flow: goto, coroutines, break $n... Data representation: structs, inheritance, enums, ADTs... Overloading: operators, closures, operator()... Metaprogramming: templates, macros...

4

slide-18
SLIDE 18

How are languages made expressive?

Control flow: goto, coroutines, break $n... Data representation: structs, inheritance, enums, ADTs... Overloading: operators, closures, operator()... Metaprogramming: templates, macros...

4

slide-19
SLIDE 19

How are languages made expressive?

Control flow: goto, coroutines, break $n... Data representation: structs, inheritance, enums, ADTs... Overloading: operators, closures, operator()... Metaprogramming: templates, macros...

4

slide-20
SLIDE 20

How are languages made expressive?

Control flow: goto, coroutines, break $n... Data representation: structs, inheritance, enums, ADTs... Overloading: operators, closures, operator()... Metaprogramming: templates, macros...

4

slide-21
SLIDE 21

Macros

Fundamentally, macros are code (executed at compile time) that computes code. (Is a compiler a macro? Is moc a macro? No. Should also say macros must exist at the same level as the target code.) Long history: LISP had Fexprs since the ’60s.

5

slide-22
SLIDE 22

Macros

Fundamentally, macros are code (executed at compile time) that computes code. (Is a compiler a macro? Is moc a macro? No. Should also say macros must exist at the same level as the target code.) Long history: LISP had Fexprs since the ’60s.

5

slide-23
SLIDE 23

Macros

Fundamentally, macros are code (executed at compile time) that computes code. (Is a compiler a macro? Is moc a macro? No. Should also say macros must exist at the same level as the target code.) Long history: LISP had Fexprs since the ’60s.

5

slide-24
SLIDE 24

Why Macros? (1)

So, why macros? Many arguments against: make code unaccessible, hide complexity, break abstraction boundaries... A lot of modern languages forswear metaprogramming altogether: Golang, Python...

6

slide-25
SLIDE 25

Why Macros? (1)

So, why macros? Many arguments against: make code unaccessible, hide complexity, break abstraction boundaries... A lot of modern languages forswear metaprogramming altogether: Golang, Python...

6

slide-26
SLIDE 26

Why Macros? (1)

So, why macros? Many arguments against: make code unaccessible, hide complexity, break abstraction boundaries... A lot of modern languages forswear metaprogramming altogether: Golang, Python...

6

slide-27
SLIDE 27

Why Macros? (2)

However, there are plenty of advantages: Simplify repetitive code. Ever have to draw 16 triangles with correct normals and texture coordinates in immediate mode OpenGL? Now try doing 16 variants of the above in a tight inner loop (e.g. marching cubes). Zero-overhead debugging. Unfortunately, -O0 still mostly means nothing is inlined, and -O1 means the value that causes your bug has probably been optimised out.

7

slide-28
SLIDE 28

Why Macros? (2)

However, there are plenty of advantages: Simplify repetitive code. Ever have to draw ∼16 triangles with correct normals and texture coordinates in immediate mode OpenGL? Now try doing 16 variants of the above in a tight inner loop (e.g. marching cubes). Zero-overhead debugging. Unfortunately, -O0 still mostly means nothing is inlined, and -O1 means the value that causes your bug has probably been optimised out.

7

slide-29
SLIDE 29

Why Macros? (2)

However, there are plenty of advantages: Simplify repetitive code. Ever have to draw ∼16 triangles with correct normals and texture coordinates in immediate mode OpenGL? Now try doing 16 variants of the above in a tight inner loop (e.g. marching cubes). Zero-overhead debugging. Unfortunately, -O0 still mostly means nothing is inlined, and -O1 means the value that causes your bug has probably been optimised out.

7

slide-30
SLIDE 30

Why Macros? (3)

Domain-specific languages. Common example for C: packet (de)serialisation in network code. With sufficiently powerful macro system, can write shaders, packet filters etc. in-line. Configuration. Maybe build system fashions have moved on, but the entire Linux ecosystem was still built on autoconf. Macros provide a clean, programmer-controlled interface for outside tooling to reshape code and adapt it to circumstances.

8

slide-31
SLIDE 31

Why Macros? (3)

Domain-specific languages. Common example for C: packet (de)serialisation in network code. With sufficiently powerful macro system, can write shaders, packet filters etc. in-line. Configuration. Maybe build system fashions have moved on, but the entire Linux ecosystem was still built on autoconf. Macros provide a clean, programmer-controlled interface for outside tooling to reshape code and adapt it to circumstances.

8

slide-32
SLIDE 32

Why Macros? (4)

Build your own language features. C/C++ keep adding functionality. As it stands, all of it has to be supported by the compiler. Google “gcc compiler bug”, lots of scary examples... But most of the ++ part of C++ could easily be implemented by macros outputting C, given sufficient power! (Get closer to the ideal of research languages? Lean, verified core; fancy features get converted to it at first compilation pass)

9

slide-33
SLIDE 33

Why Macros? (4)

Build your own language features. C/C++ keep adding functionality. As it stands, all of it has to be supported by the compiler. Google “gcc compiler bug”, lots of scary examples... But most of the ++ part of C++ could easily be implemented by macros outputting C, given sufficient power! (Get closer to the ideal of research languages? Lean, verified core; fancy features get converted to it at first compilation pass)

9

slide-34
SLIDE 34

Why Macros? (4)

Build your own language features. C/C++ keep adding functionality. As it stands, all of it has to be supported by the compiler. Google “gcc compiler bug”, lots of scary examples... But most of the ++ part of C++ could easily be implemented by macros outputting C, given sufficient power! (Get closer to the ideal of research languages? Lean, verified core; fancy features get converted to it at first compilation pass)

9

slide-35
SLIDE 35

Why Macros? (4)

Build your own language features. C/C++ keep adding functionality. As it stands, all of it has to be supported by the compiler. Google “gcc compiler bug”, lots of scary examples... But most of the ++ part of C++ could easily be implemented by macros outputting C, given sufficient power! (Get closer to the ideal of research languages? Lean, verified core; fancy features get converted to it at first compilation pass)

9

slide-36
SLIDE 36

Why Macros? (4)

Build your own language features. C/C++ keep adding functionality. As it stands, all of it has to be supported by the compiler. Google “gcc compiler bug”, lots of scary examples... But most of the ++ part of C++ could easily be implemented by macros outputting C, given sufficient power! (Get closer to the ideal of research languages? Lean, verified core; fancy features get converted to it at first compilation pass)

9

slide-37
SLIDE 37

The C preprocessor

C actually has a macro system. Unfortunately, it’s rather limited:

1

#define TEST(1) 1+TEST(1) //this won't loop forever :(

There are some workarounds, but none will give you true recursion. Also, can only create “fake variables” and “fake function calls”.

10

slide-38
SLIDE 38

Other macro systems

LISP: Arbitrary LISP code operating on LISP code. This works because LISP code is approximately thing::=(thing ...)|name|value! Writing down a type

  • f C/C++ expressions is an MEng thesis (and it probably won’t

be quite right). Rust: DSL for matching, capturing and emitting token streams. Good start, but the Rust team fell for the “don’t surprise the reader” meme.

11

slide-39
SLIDE 39

Other macro systems

LISP: Arbitrary LISP code operating on LISP code. This works because LISP code is approximately thing::=(thing ...)|name|value! Writing down a type

  • f C/C++ expressions is an MEng thesis (and it probably won’t

be quite right). Rust: DSL for matching, capturing and emitting token streams. Good start, but the Rust team fell for the “don’t surprise the reader” meme.

11

slide-40
SLIDE 40

Towards a better macro system for C/C++

Let’s build something like that for C/C++! In fact, I did: https://github.com/blackhole89/macros

12

slide-41
SLIDE 41

Towards a better macro system for C/C++

Let’s build something like that for C/C++! In fact, I did: https://github.com/blackhole89/macros

12

slide-42
SLIDE 42

Simple example

1

struct { int value; LinkedList *next; } LinkedList;

2 3

// define recursive macro to create a linked list

4

@define MakeList {

5

( {@^[,]$head, @^$tail} ) => (

6

new LinkedList( {$head, MakeList {$tail}} )

7

)

8

( {@^[,]$singleton} ) => (

9

new LinkedList( {$singleton, NULL} )

10

)

11

( {} ) => ( NULL )

12

}

13 14

// create a linked list with 5 elements

15

LinkedList *l = MakeList {1,2,3,4,5};

13

slide-43
SLIDE 43

Basic summary

@define name {...} creates a new macro name; {...} contains a series of pattern-outcome pairs. Whenever name is encountered, the parser tries to match the tokens following it to patterns in order. If a match succeeds, the outcome is processed and then emitted. Finally, processing proceeds with the first token that was not consumed by the match. A successful pattern match may capture tokens or streams of tokens into variables, which are available for the processing of the outcome.

14

slide-44
SLIDE 44

More features

The pattern language contains facilities for matching various common grammars such as separated lists or everything until a particular token is encountered. More complex grammars should be implemented by capturing everything and rerunning the matcher on it using match. Also support S-expression-valued variables and iteration over them, basic arithmetic, basic string processing.

15

slide-45
SLIDE 45

More features

The pattern language contains facilities for matching various common grammars such as separated lists or everything until a particular token is encountered. More complex grammars should be implemented by capturing everything and rerunning the matcher on it using match. Also support S-expression-valued variables and iteration over them, basic arithmetic, basic string processing.

15

slide-46
SLIDE 46

Algebraic datatypes

Can add algebraic datatypes to C++ (∼208 lines of macros):

1

datatype List<T> = Nil | Cons(T,List<T>) ;

2 3

/* flatten a list of lists */

4

template<class T> List<T> unions(List<List<T>> ls)

5

{

6

match(ls) {

7

case Cons(Cons(&x,&xs),&ys):

8

return Cons(x, unions(Cons(xs,ys)));

9

case Cons(Nil,&ys):

10

return unions(ys);

11

case Nil:

12

return Nil<T>;

13

}

14

}

16

slide-47
SLIDE 47

Painless reflection

By re@defineing the keyword class, we can get reflection (and serialisation, and Java-style annotations...):

1

class TestClass {

2

int test; // (rest omitted for space)

3

};

4 5

int main(int argc, char* argv[])

6

{

7

printf("Members of class TestClass:\n");

8

for( auto a : Reflect<TestClass>::members ) {

9

printf(" %s\n",a.c_str());

10

}

11

return 0;

12

}

17

slide-48
SLIDE 48

Thoughts

Macros are fun. Can tweak the language to be more like you want it to be without embarking on a project to write your own language. For stuff like reflection, it’s easy to beat ISO (does their compromise solution make anyone happy?) Turns out that this approach to macros basically gives you TeX, with all its subtleties (\ expandafter, anyone?). Many nontrivial design choices surrounding variable scope and substitution behaviour.

18

slide-49
SLIDE 49

Thoughts

Macros are fun. Can tweak the language to be more like you want it to be without embarking on a project to write your own language. For stuff like reflection, it’s easy to beat ISO (does their compromise solution make anyone happy?) Turns out that this approach to macros basically gives you TeX, with all its subtleties (\ expandafter, anyone?). Many nontrivial design choices surrounding variable scope and substitution behaviour.

18

slide-50
SLIDE 50

Thoughts

Macros are fun. Can tweak the language to be more like you want it to be without embarking on a project to write your own language. For stuff like reflection, it’s easy to beat ISO (does their compromise solution make anyone happy?) Turns out that this approach to macros basically gives you TeX, with all its subtleties (\ expandafter, anyone?). Many nontrivial design choices surrounding variable scope and substitution behaviour.

18

slide-51
SLIDE 51

Future work

Make the preprocessor more aware of C/C++. Maybe one day it will be able to distinguish less-than from template brackets. Support more flexible grammar rules? Custom operators with fixity? (Yes, I really want C++ ML...) Once we can write Haskell as a C++ DSL, we’re officially self-hosting :) Thanks for listening!

19

slide-52
SLIDE 52

Future work

Make the preprocessor more aware of C/C++. Maybe one day it will be able to distinguish less-than from template brackets. Support more flexible grammar rules? Custom operators with fixity? (Yes, I really want C++ ∪ ML...) Once we can write Haskell as a C++ DSL, we’re officially self-hosting :) Thanks for listening!

19

slide-53
SLIDE 53

Future work

Make the preprocessor more aware of C/C++. Maybe one day it will be able to distinguish less-than from template brackets. Support more flexible grammar rules? Custom operators with fixity? (Yes, I really want C++ ∪ ML...) Once we can write Haskell as a C++ DSL, we’re officially self-hosting :) Thanks for listening!

19

slide-54
SLIDE 54

Future work

Make the preprocessor more aware of C/C++. Maybe one day it will be able to distinguish less-than from template brackets. Support more flexible grammar rules? Custom operators with fixity? (Yes, I really want C++ ∪ ML...) Once we can write Haskell as a C++ DSL, we’re officially self-hosting :) Thanks for listening!

19