Overview
Comment: | fix kmheapa() and add kmheapf() |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
5279674525478386b960cc2c3cea164b |
User & Date: | lexi on 2019-08-18 10:20:30 |
Other Links: | manifest | tags |
Context
2019-08-18
| ||
11:34 | add memory functions check-in: 5393623a84 user: lexi tags: trunk | |
10:20 | fix kmheapa() and add kmheapf() check-in: 5279674525 user: lexi tags: trunk | |
2019-07-27
| ||
05:28 | port header macro files to m4; delete gpp infra; fix glaring syntax errors in kcore/type.h check-in: 0c20d256a6 user: lexi tags: trunk | |
Changes
Modified kcli/kcli.md from [91db6a8cad] to [09b7990027].
21 21 * `size_t optc` - the number of options in the list to process. 22 22 23 23 a kcli_set might be used like so: 24 24 25 25 #include <k/core.h> 26 26 #include <k/io.h> 27 27 #include <k/cli.h> 28 - u8 entry(kenv e) { 28 + stat entry(kenv e) { 29 29 kcli_flag aardvark; 30 30 kcli_flag zebra; 31 31 char* user; 32 32 char* password; 33 33 long age; 34 - kcli_param* params = { 34 + kcli_param params[] = { 35 35 { "user", kcli_param_string, kcli_class_required, 36 36 &user, "the user to log in as" } 37 37 // or Kcli_param(user,string,required,"the user to log in as"), 38 38 39 39 { "age", kcli_param_dec, kcli_class_optional, 40 40 &age, "the age of the user" } 41 41 // or Kcli_param(age,dec,optional,"the age of the user"), 42 42 }; 43 - kcli_opt* options = { 43 + kcli_opt options[] = { 44 44 { 'a', "aardvark", kcli_opt_flag, &aardvark, 45 45 "a nocturnal burrowing mammal" }, 46 46 // or Kcli_opt(aardvark, 'a', flag, "a nocturnal burrowing mammal") 47 47 { 'z', "zebra", kcli_opt_flag, &zebra, 48 48 "a striped equine" }, 49 49 { 'p', "password", kcli_opt_string, &password, 50 50 "the password to log in with" } 51 51 }; 52 - kcli_set me = { 52 + kcli_set argset = { 53 53 "demo", e.argc, e.argv, 54 54 "a demonstration of the kcli_set type", 55 55 params, Kmsz(params), 56 56 options, Kmsz(options) 57 57 }, 58 - size_t args_parsed = kcli_parse(&me); 58 + size_t args_parsed = kcli_parse(&argset); 59 59 if (args_parsed == 0) { kcli_usage(&me, e.err); return 1; } 60 60 61 61 return 0; 62 62 } 63 63 64 64 ### struct kcli_opt 65 65 a `kcli_opt` is a representation of a command-line flag and its function. each option must have a unique `id` and/or a unique `name`. ................................................................................ 76 76 * `kcli_opt_string` - flag tells kcli to add a string to the list of expected parameters; appropriate string will be returned 77 77 * `kcli_opt_oct` - flag tells kcli to add an octal number to the list of expected parameters 78 78 * `kcli_opt_dec` - flag tells kcli to add a decimal number to the list of expected parameters 79 79 * `kcli_opt_hex` - flag tells kcli to add a hexdecimal number to the list of expected parameters 80 80 * `kcli_opt_flag` - flag is an option: will return `kcli_flag_on` if entered at least once, `kcli_flag_off` otherwise. 81 81 * `kcli_opt_toggle` - flag toggles value on and off: will return `kcli_flag_on` if entered an odd number of times, `kcli_flag_off` otherwise. 82 82 * `kcli_opt_accumulate` - flag increments a value every time it is entered; often used to implement `-v (--verbose)`-style options (e.g. `-vvvv` would return a value of `4`). 83 + * `kcli_opt_enum` - flag is one of a series of enumerated values, which will be matched against a table to yield the associated integer. 83 84 84 85 ### struct kcli_param 85 86 `kcli_param` describes a parameter that may be passed to the program whether or not any flags are passed. 86 87 87 88 * `const char* name` - a short name for the parameter 88 89 * `kcli_param_kind kind` - the kind of parameter passed 89 90 * `kcli_class class` - whether or not the parameter is optional
Modified kcore/def.h.m from [694c1d32cc] to [2981fff8c9].
1 1 dnl kcore/def.h.m → <k/def.h> 2 2 dnl ~ lexi hale <lexi@hale.su> 3 3 dnl this file gathers information on the environment it's 4 4 dnl being compiled in, setting macros that other headers 5 5 dnl need. it will be emitted as <k/def.h>. 6 -dnl vim: ft=c 6 +dnl vim: ft=m4 7 7 #ifndef KIdef 8 8 #define KIdef 9 -define(`def',`#define $1 $2') 9 +define(`_atom',0)dnl 10 +define(`def',`#define $1 $2')dnl 11 +define(`defatom',`def($1,$2$3)')dnl 12 +define(`newatom',`def($1,_atom) 13 + define(`_atom',incr(_atom))')dnl 10 14 11 15 ifdef(`atom_target_bits',` 12 16 define(`target',`atom_target_arch.atom_target_os.atom_target_bits') 13 17 def(KVbits,atom_target_bits)',` 14 18 define(`target',atom_target_arch.atom_target_os)') 15 19 16 -def(KVtarget,target) 17 -def(KVos,atom_target_os) 18 -def(KVarch,atom_target_arch) 20 +newatom(KA_os_lin)dnl 21 +newatom(KA_os_fbsd)dnl 22 +newatom(KA_os_obsd)dnl 23 +newatom(KA_os_nbsd)dnl 24 +newatom(KA_os_dar)dnl 25 +newatom(KA_os_osx)dnl 26 +newatom(KA_os_and)dnl 27 +newatom(KA_os_hai)dnl 28 +newatom(KA_os_win)dnl 29 + 30 +newatom(KA_arch_x86)dnl 31 +newatom(KA_arch_arm)dnl 32 +newatom(KA_arch_ppc)dnl 33 +newatom(KA_arch_mips)dnl 34 +newatom(KA_arch_itan)dnl 35 + 36 +defatom(KVos,KA_os_,atom_target_os) 37 +defatom(KVarch,KA_arch_,atom_target_arch) 19 38 20 39 ifelse(target_unix,`yes', 21 40 `def(`KFenv_unix',) 22 41 def(`KFenv_posix',)',` 23 42 ifelse(target_posix,`yes', 24 43 `def(KFenv_posix)')') 25 44 26 45 #define Kpragma(p) _Pragma(#p) 27 46 #if defined(__GNUC__) || defined(__clang__) 28 47 # define Kerror(msg) Kpragma(GCC error #msg) 29 48 #else 30 49 # define Kerror(msg) Kpragma(message #msg) 31 50 #endif 32 -#define Knoimpl(fn) Kerror(no implementation of fn for platform [target]) 51 +def(`Knoimpl(fn)', Kerror(no implementation of fn for platform target)) 33 52 34 53 #endif
Modified kcore/testbin.exe.c from [5796abefa5] to [8406c82300].
5 5 6 6 struct object { 7 7 u8 a; 8 8 s16 b; 9 9 bool c; 10 10 }; 11 11 12 -kbad entry(kenv e) { 12 +stat_long entry(kenv e) { 13 13 const char msg[] = "hello from libk\n"; 14 14 ksraw ptr = { Kmsz(msg), msg }; 15 15 16 16 bool maybe = true; 17 17 maybe = no; 18 18 19 19 if (kiosend(e.std, ptr, null) == kiocond_ok) { 20 20 /* great, continue */ 21 21 } else { 22 22 return kbad_io; 23 23 } 24 24 25 25 struct object* block = kmheapa(sizeof (struct object) * 4); 26 - if (block == null) return kbad_mem; else return kbad_ok; 26 + if (block == null) return kbad_mem; 27 27 28 28 block[1].a = 5; 29 + 30 + if (kmheapf(block) != kmcond_ok) return kbad_mem; 29 31 30 32 return kbad_ok; 31 33 }
Modified kmem/heapa.fn.c from [f32eb209ee] to [aa6d2182a2].
1 1 #include <k/core.h> 2 2 #include <k/def.h> 3 +#include <k/type.h> 4 +#include <posix.h> 3 5 /* heapa.c - kmheapa() "heap alloc" 4 6 * ~ lexi hale <lexi@hale.su> 5 7 * kmheapa() allocates a pointer on the heap à la libc malloc() 6 8 * see also: kmheapf() "heap free" 7 9 */ 8 10 9 11 /* we define all platform functions here, 10 12 * whether or not they're for the correct 11 13 * platform - only the ones actually called 12 14 * by the generated code will be linked, 13 15 * linker errors are our friend here! */ 14 -extern void* kmem_posix_mmap(void* addr, 16 +extern void* kmem_platform_mmap(void* addr, 15 17 unsigned long sz, unsigned long prot, unsigned long flags, 16 18 unsigned long fd, unsigned long off); 17 19 18 -enum posix_prot { 19 - posix_prot_none = 0, 20 - posix_prot_read = 1 << 0, 21 - posix_prot_write = 1 << 1, 22 - posix_prot_exec = 1 << 2 23 -}; 24 - 25 -enum posix_map { 26 - posix_map_shared = 1, 27 - posix_map_private = 2 28 -}; 29 - 30 -enum posix_flag { 31 - posix_flag_fixed = 0x10, 32 - posix_flag_anonymous = 0x20, 33 - 34 - /* platform flags */ 35 - posix_flag_linux_hugetlb = 0x40000 36 -}; 37 - 38 20 void* kmheapa(sz len) { 39 21 /* allocate an object on the heap and return 40 22 * a pointer, or NULL if the allocation failed. */ 41 23 void* val; 42 24 43 25 # ifdef KFenv_posix 44 - /* posix APIs - we've got it easy */ 45 - val = kmem_posix_mmap(null, len, posix_prot_read | posix_prot_write, 46 - posix_flag_anonymous, -1, 0); 26 + /* posix APIs - we've got it easy. currently for nonlinear 27 + * heap allocation kmheapa simply uses m(un)map and lets the 28 + * kernel worry about it. it may ultimately be worth replacing 29 + * this with a more sophisticated implementation, most likely 30 + * an existing allocator like jemalloc, though i'm wary of 31 + * including outside code - it creates a licensing mess and 32 + * i'd prefer libk to be AGPLv3 across the board. possibility: 33 + * include hooks for multiple allocators, allowing the user 34 + * to select & link in her preferred allocator at compile time? */ 35 + 36 + /* because munmap needs to be informed of the size of 37 + * the region it is going to unmap, we need to store 38 + * that information in the allocated region itself. 39 + * the user will be given a pointer that can be 40 + * adjusted to point a field of type size_t that 41 + * contains the size of the allocate space.*/ 42 + 43 + sz const region_total = len + sizeof len; 44 + ubyte* const region = kmem_platform_mmap(null, region_total, 45 + posix_prot_read | posix_prot_write, 46 + posix_flag_anonymous | posix_map_shared, -1, 0); 47 47 /* impl note: while per manpage fd is "ignored" 48 48 * for MAP_ANONYMOUS, "some implementations" require 49 49 * a value of -1 */ 50 50 51 - if (val == (void*) -1) return null; 51 + if (region == (void*) -1) return null; 52 52 /* worth retrieving errno? discuss */ 53 53 54 + *((sz*)region) = len; 55 + val = region + sizeof len; 56 + 54 57 # else 55 58 Knoimpl(kmheapa,KVos); 56 59 # error missing implementation 57 60 # endif 58 61 59 62 return val; 60 63 }
Modified kmem/kmem.md from [18509d6a57] to [5405a3e53e].
1 1 # kmem 2 2 3 3 **kmem** is a libk module that contains various functions for memory allocation and deallocation. it uses the **short** naming convention with the glyph `m`. 4 4 5 +kmem allocators can work in several different ways. they can allocate memory directly from the heap (like `kmheapa()` and `kmlina()`), use a header that has already been allocated by another function, or allocate memory only from a pre-allocated pool. linear allocation with pool allocation is particularly useful, as it permits the very rapid allocation and deallocation of lots of objects with only a few adjustments to the heap, and no possibility of fragmentation or need for expensive algorithms like `malloc()` or `kmheapa()` 6 + 5 7 ## module functions 6 8 7 -**kmem** supplies two module-level functions, used to interact with the `kmptr` container type. 9 +kmem supplies two module-level functions, used to interact with the `kmptr` container type. 8 10 9 11 * `kmfree(kmptr) → void` - free, downref, or ignore the pasted object as appropriate 10 12 * `kmshred(kmptr) → void` - free, downref, or ignore the pasted object as appropriate. if deallocating, zero its contents 11 13 * `kmstat(void*) → kmptr` - convenience function to wrap a pointer to a non-managed object in a `kmptr` struct, so it can be passed to functions that accept arbitrary objects. `kmptr p = kmstat(raw)` is equivalent to `kmptr p = { kmkind_none, raw, NULL }`. 12 14 * `kmtaint(&kmptr) → void` - "taints" a `kmptr` object by setting it to be shredded when freed. this may be desirable if the object pointed to contains privileged information. 13 15 14 16 ## types ................................................................................ 26 28 27 29 28 30 ### kmkind 29 31 30 32 `kmkind` is an enum that specifies an allocation function. 31 33 32 34 * `kmkind_none` - no allocation 33 - * `kmkind_heap` - heap allocation 35 + * `kmkind_lin` - linear heap allocation 36 + * `kmkind_heap` - random heap allocation 34 37 * `kmkind_pool` - pool allocation 35 38 * `kmkind_ref` - reference-counting allocation 36 39 * `kmkind_tree` - tree allocation 37 40 38 41 ### kmptr 39 42 40 43 kmem functions can operate on both raw pointers and the `kmptr` struct type. `kmptr` is a generic struct that can contain any kind of pointer. this is useful if you wish to allocate different objects in different manners, but pass them on into a single interface. ................................................................................ 46 49 * `kmkind kind` - codes the type of pointer; `kmkind_none` indicates a non-allocated pointer to a static (global or on-stack) object. 47 50 * `kmshred shred` - an enum. if `kmshred_yes`, the value will be zeroed or otherwise made unreadable on free. if no, `kmfree` will consult `src` for shred policy if it is not NULL. 48 51 * `void* ref` - the raw pointer enclosed by `cell` 49 52 * `kmcell* cell` - a pointer to an object enclosure, typically either a memory pool or a referencing-counting object. NULL if not needed. 50 53 51 54 the convenience function `kmstat(void*) → kmptr` wraps a pointer to a static object in a `kmptr` struct. 52 55 53 -### kmcell 56 +### struct kmcell 54 57 55 -`kmcell` is a stub struct used to disambiguate between source types.a "source" is an object that can hold an allocated object, such as the heap, a memory pool, a fixed-length array on stack, or a fixed-length global array. all values produced by a kmem allocation function point to within a `kmcell`. 58 +`kmcell` is a stub struct used to disambiguate between source types. a "source" is an object that can hold an allocated object, such as the heap, a memory pool, a fixed-length array on stack, or a fixed-length global array. all values produced by a kmem allocation function can be cast to `kmcell*`, and have an intial field `id` that contains a `kmcell`. 56 59 57 60 * `kmkind kind` - kind of cell 58 61 * `size_t size` - size of cell (data plus all fields) 59 62 * `kmshred shred` - shredding flag 60 63 61 -### kmref 64 +### struct kmref 62 65 63 66 `kmref` is a struct that constitutes the in-memory representation of a reference-counted cell. 64 67 65 - * `kmkind kind = kmkind_ref` - kind of cell 66 - * `size_t sz` - size of cell (data plus all fields) 67 - * `kmshred shred` - shredding flag 68 + * `kmcell id = { .kind = kmkind_ref, … } ` - kind of cell 68 69 * `size_t refs` - number of active references 69 70 * `kmcell* src` - source, if any 70 71 * `char data[]` - content of cell 71 72 72 -### kmnode 73 +### struct kmnode 73 74 74 -`kmnode` is a struct that constitutes the in-memory representation of a tree node. 75 +`kmnode` is the header struct for tree nodes. all tree nodes pointers can yield a `kmnode` structure by subtracting `sizeof (kmnode)` from the pointer. a utility function and macro are made available to automate this safely. 75 76 76 - * `kmkind kind = kmkind_tree` - kind of cell 77 - * `size_t sz` - size of cell (data plus all fields) 78 - * `kmshred shred` - shredding flag 77 + * `kmcell id = { .kind = kmkind_tree, … } ` - kind of cell 79 78 * `kmnode* parent` - parent node 80 79 * `kmnode* child` - first child node 81 80 * `kmnode* lastchild` - last child node 82 81 * `kmnode* prev` - previous sibling, NULL if first 83 82 * `kmnode* next` - next sibling, NULL if last 84 - * `char data[]` - content of cell 85 83 86 -### kmpool 84 +### struct kmpool 87 85 88 - * `kmkind kind = kmkind_pool` - indicates the kind of source 89 - * `size_t sz` - size of cell (data plus all fields) 90 - * `kmshred shred` - shredding flag 86 + * `kmcell id = { .kind = kmkind_pool, … } ` - kind of cell 91 87 * `size_t cellsz` - size of individual pool cells 92 88 * `kmpoolcell* top` - pointer to most recently allocated pool cell 93 89 * `kmpoolcell* bottom` - pointer to most recently freed pool cell 94 90 * `kmpoolcell data[]` - content of cell 95 91 96 -#### kmpoolcell 92 +#### struct kmpoolcell 97 93 98 94 * `kmpoolcell* last` - pointer to last element allocated before this one 99 95 * `char data[]` - pool data 100 96 101 -### kmshred 97 +### enum kmshred 102 98 103 99 `kmshred` is an enum used to indicate whether an object should be "shredded" (written over) in memory when it's deleted. this is a useful means to ensure that privileged information is not accidentally left in memory after use. if the shredding mechanism is not useful, compile libk with the flag `KFmem_noshred` to exclude its functions and fields. 104 100 105 - * `kmshred_yes` - marks an object to shred on free 106 - * `kmshred_no` - marks an object not to shred on free 101 + * `kmshred_no = 0` - marks an object not to shred on free 102 + * `kmshred_yes = 1` - marks an object to shred on free 107 103 108 104 ## naming convention 109 105 110 106 kmem function names are based on the **method** of allocation and the **action** being performed. methods are listed in the section below. kmem defines a number of standardized actions, though not every method uses every action. the character listed in brackets is suffixed to the name of the method to produce a function name: for instance, `kmheapa` will allocate memory on the heap, while `kmrefd` will decrement the reference count of its argument. 111 107 112 108 * initialize [i] - initializes a memory store on the heap 113 109 * initialize fixed [if] - initialize a memory store on the stack or in a fixed-size global ................................................................................ 118 114 * free [f] - free a section of memory, either decrementing a reference count or returning it to whatever pool it came from. 119 115 * shred [s] - destroy whatever was in the segment of memory, then return it to the pool it came from. 120 116 * destroy [x] - tears down a memory store 121 117 * upref [u] - increments a reference counter 122 118 123 119 ## methods 124 120 125 -kmem currently supports the following methods of memory management, along with which methods are defined for it. (note that `a` implies `z` and `f` implies `s`). a method may be excluded from a libk binary by defining the flag `KFmem_no[name]`, e.g. `KFmem_noheap` 121 +kmem currently supports the following methods of memory management, along with which methods are defined for it. (note that `a` implies `z` and `f` implies `s`). a method may be excluded from a libk binary by defining the flag `KFmem_no[name]`, e.g. `KFmem_noheap`. 122 + 123 +the fastest allocator is the linear allocator, which should be sufficient for most simple programs. it allocates and deallocates memory simply by resizing the stack; there is no fragmentation, but objects must be freed in the order they are allocated. however, entire groups of objects can be freed at once at very little cost. 126 124 127 - * `heap` [af] - standard heap allocation 125 + * `lin` [iax] - linear heap allocator 126 + * `kmlini(void) → void*` - return a pointer to the current top of the heap 127 + * `kmlina(size_t) → void*` - allocate space on the heap and increase its size appropriately 128 + * `kmlinz(size_t) → void*` - allocate zero-filled space on the heap and increase its size appropriately 129 + * `kmlinx(void*) → void*` - returns the top of the heap to the location specified, freeing all memory allocated since the call to kmlini() or `kmlina()` that produced it 130 + * `heap` [af] - random heap allocation 128 131 * `kmheapa(size_t) → void*` - allocate 129 132 * `kmheapz(size_t) → void*` - zero-allocate 130 133 * `kmheapao(size_t) → kmptr` - allocate pointer object 131 134 * `kmheapzo(size_t) → kmptr` - zero-allocate pointer object 132 135 * `kmheapf(void*) → void` - free 133 136 * `kmheaps(void*) → void` - shred 134 137 * `ref` [afu] - reference-counted heap object
Modified kmem/mem.h from [e8670496c8] to [09be45a2b3].
5 5 #ifndef KFclean 6 6 # define Kmsz(e) ( sizeof (e) / sizeof (e) [0] ) 7 7 #endif 8 8 9 9 #ifdef __cplusplus 10 10 extern "C" { 11 11 #endif 12 + 13 +typedef enum kmcond { 14 + kmcond_ok, 15 + kmcond_bad_address, 16 +} kmcond; 12 17 13 18 typedef enum kmkind { 14 19 kmkind_none, 15 20 kmkind_heap, 16 21 kmkind_pool, 17 22 kmkind_ref, 18 23 kmkind_tree ................................................................................ 25 30 26 31 typedef struct kmcell { 27 32 kmkind kind; 28 33 sz size; 29 34 kmshred shred; 30 35 sz refs; 31 36 struct kmcell* src; 32 - char data[]; 33 37 } kmcell; 34 38 35 39 typedef struct kmptr { 36 40 kmkind kind; 37 41 kmshred shred; 38 42 void* ref; 39 43 kmcell* cell; 40 44 } kmptr; 41 45 42 46 /* heap functions */ 43 47 44 -void* kmheapa(sz); 45 -void kmheapf(void*); 48 +void* kmheapa(sz); 49 +kmcond kmheapf(void*); 46 50 47 51 #ifdef __cplusplus 48 52 } 49 53 #endif 50 54 51 55 #endif
Modified kmem/platform.mmap.fn.x86.lin.64.s from [0ee1179d8c] to [ceb93a428c].
1 1 bits 64 2 2 %include "../arch/x86.lin.64.s" 3 3 %include "../arch/x86.cdecl.64.s" 4 4 ; vim: ft=nasm 5 5 6 -global kmem_posix_mmap 7 -kmem_posix_mmap: 6 +global kmem_platform_mmap 7 +kmem_platform_mmap: 8 8 ; to call mmap, we need to translate the cdecl64 9 9 ; register arguments to their appropriate syscall64 10 10 ; registers. these are mostly the same, with one 11 11 ; obnoxious exception. the NOPs have been written 12 12 ; in as comments to aid in understanding. 13 13 14 14 mov sys.reg.1, ccall.reg.0 ;nop - rdi → rdi ................................................................................ 18 18 mov sys.reg.5, ccall.reg.4 ;nop - r8 → r8 19 19 mov sys.reg.6, ccall.reg.5 ;nop - r9 → r9 20 20 21 21 mov sys.reg.0, sys.mmap 22 22 sys.call 23 23 24 24 mov ccall.reg.ret, sys.reg.ret ; rax → rdi 25 + ret
Modified makefile from [7c13625dc8] to [d3e7936df2].
1 1 export OUT = $(PWD)/out 2 2 3 3 # TODO: calculate these using $(MAKE_HOST) 4 4 export ARCH = x86 5 5 export OS = lin 6 6 export BITS = 64 7 +export ROOT = $(PWD) 7 8 export TMP = $(PWD)/tmp 8 9 9 10 ifneq ($(BITS),) 10 11 export TARGET = $(ARCH).$(OS).$(BITS) 11 12 else 12 13 export TARGET = $(ARCH).$(OS) 13 14 endif
Modified modmake from [4d2e5850b0] to [5ed7cc8a39].
9 9 headers = $(wildcard *.h) $(gen-headers) $(patsubst %.m,%,$(wildcard *.h.m)) 10 10 11 11 tools = $(filter %.exe.c, $(src)) 12 12 nontools = $(filter-out %.exe.c, $(src)) 13 13 cobjects = $(filter %.c, $(nontools)) 14 14 sobjects = $(filter %.${TARGET}.s, $(nontools)) 15 15 16 -cflags = -std=c11 -isystem ${OUT} -fPIC -nostdlib ${COMPLIB} -L${OUT} 16 +cflags = -std=c11 -isystem ${OUT} -isystem ${ROOT}/arch -fPIC -nostdlib ${COMPLIB} -L${OUT} 17 17 18 18 m-env = atom_target_arch=${ARCH} 19 19 m-env += atom_target_os=${OS} 20 20 ifneq (${BITS},) #!!! ifdef does NOT work with environment variables 21 21 m-env += atom_target_bits=${BITS} 22 22 endif 23 23 m-env += target_posix=${POSIX}