From 9739a96b0b91c70da24d2f7af2c80c52aab44cc8 Mon Sep 17 00:00:00 2001 From: pkova Date: Fri, 13 Sep 2024 15:03:17 +0300 Subject: [PATCH 1/5] noun: fix some left shiting undefined behavior --- pkg/noun/events.c | 8 ++++---- pkg/noun/hashtable.c | 8 ++++---- pkg/noun/jets.c | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/noun/events.c b/pkg/noun/events.c index a90403b9da..66db0fef98 100644 --- a/pkg/noun/events.c +++ b/pkg/noun/events.c @@ -305,12 +305,12 @@ u3e_fault(u3_post low_p, u3_post hig_p, u3_post off_p) } #endif - if ( u3P.dit_w[blk_w] & (1 << bit_w) ) { + if ( u3P.dit_w[blk_w] & ((c3_w)1 << bit_w) ) { fprintf(stderr, "loom: strange page (%d): %x\r\n", pag_w, off_p); return u3e_flaw_sham; } - u3P.dit_w[blk_w] |= (1 << bit_w); + u3P.dit_w[blk_w] |= ((c3_w)1 << bit_w); if ( u3P.eph_i ) { if ( _ce_flaw_mmap(pag_w) ) { @@ -947,7 +947,7 @@ _ce_loom_track_north(c3_w pgs_w, c3_w dif_w) for ( ; i_w < max_w; i_w++ ) { blk_w = i_w >> 5; bit_w = i_w & 31; - u3P.dit_w[blk_w] &= ~(1 << bit_w); + u3P.dit_w[blk_w] &= ~((c3_w)1 << bit_w); } max_w += dif_w; @@ -969,7 +969,7 @@ _ce_loom_track_south(c3_w pgs_w, c3_w dif_w) for ( ; i_w >= max_w; i_w-- ) { blk_w = i_w >> 5; bit_w = i_w & 31; - u3P.dit_w[blk_w] &= ~(1 << bit_w); + u3P.dit_w[blk_w] &= ~((c3_w)1 << bit_w); } max_w -= dif_w; diff --git a/pkg/noun/hashtable.c b/pkg/noun/hashtable.c index ec92f2924a..cb817890cb 100644 --- a/pkg/noun/hashtable.c +++ b/pkg/noun/hashtable.c @@ -9,11 +9,11 @@ /* CUT_END(): extract [b_w] low bits from [a_w] */ -#define CUT_END(a_w, b_w) ((a_w) & ((1 << (b_w)) - 1)) +#define CUT_END(a_w, b_w) ((a_w) & (((c3_w)1 << (b_w)) - 1)) /* BIT_SET(): [1] if bit [b_w] is set in [a_w] */ -#define BIT_SET(a_w, b_w) ((a_w) & (1 << (b_w))) +#define BIT_SET(a_w, b_w) ((a_w) & ((c3_w)1 << (b_w))) static c3_o _ch_trim_slot(u3h_root* har_u, u3h_slot *sot_w, c3_w lef_w, c3_w rem_w); @@ -104,7 +104,7 @@ _ch_node_add(u3h_node* han_u, c3_w lef_w, c3_w rem_w, u3_noun kev, c3_w *use_w) // c3_w len_w = _ch_popcount(map_w); u3h_node* nah_u = _ch_node_new(1 + len_w); - nah_u->map_w = han_u->map_w | (1 << bit_w); + nah_u->map_w = han_u->map_w | ((c3_w)1 << bit_w); for ( i_w = 0; i_w < inx_w; i_w++ ) { nah_u->sot_w[i_w] = han_u->sot_w[i_w]; @@ -196,7 +196,7 @@ _ch_two(u3h_slot had_w, u3h_slot add_w, c3_w lef_w, c3_w ham_w, c3_w mad_w) else { u3h_node* han_u = _ch_node_new(2); ret = han_u; - han_u->map_w = (1 << hop_w) | (1 << tad_w); + han_u->map_w = ((c3_w)1 << hop_w) | ((c3_w)1 << tad_w); // smaller mug fragments go in earlier slots if ( hop_w < tad_w ) { han_u->sot_w[0] = had_w; diff --git a/pkg/noun/jets.c b/pkg/noun/jets.c index 850e717b94..52e72b340d 100644 --- a/pkg/noun/jets.c +++ b/pkg/noun/jets.c @@ -280,7 +280,7 @@ _cj_warm_hump(c3_l jax_l, u3_noun huc) if ( (1 != sscanf(jet_u->fcs_c+1, "%" SCNu64, &axe_d)) || axe_d >> 32ULL || - ((1 << 31) & (axe_l = (c3_w)axe_d)) || + (((c3_w)1 << 31) & (axe_l = (c3_w)axe_d)) || (axe_l < 2) ) { u3l_log("jets: activate: bad fcs %s", jet_u->fcs_c); From b774346ed98bc8b786618c04e168860b7ee38725 Mon Sep 17 00:00:00 2001 From: pkova Date: Fri, 13 Sep 2024 15:03:51 +0300 Subject: [PATCH 2/5] lmbd: fix some unaligned access undefined behavior --- pkg/vere/db/lmdb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/vere/db/lmdb.c b/pkg/vere/db/lmdb.c index 61158e3d54..200df69fa3 100644 --- a/pkg/vere/db/lmdb.c +++ b/pkg/vere/db/lmdb.c @@ -198,7 +198,7 @@ u3_lmdb_gulf(MDB_env* env_u, c3_d* low_d, c3_d* hig_d) return c3n; } else { - fir_d = *(c3_d*)key_u.mv_data; + memcpy(&fir_d, key_u.mv_data, sizeof(c3_d)); } // read with the cursor from the end of the database @@ -206,7 +206,7 @@ u3_lmdb_gulf(MDB_env* env_u, c3_d* low_d, c3_d* hig_d) ret_w = mdb_cursor_get(cur_u, &key_u, &val_u, MDB_LAST); if ( !ret_w ) { - las_d = *(c3_d*)key_u.mv_data; + memcpy(&las_d, key_u.mv_data, sizeof(c3_d)); } // clean up unconditionally, we're done From 6a950ac9e5fac28b37fed3e7e9b4043d5cf820a9 Mon Sep 17 00:00:00 2001 From: pkova Date: Thu, 26 Sep 2024 17:24:23 +0300 Subject: [PATCH 3/5] misc: more undefined behavior left shifting fixes --- pkg/c3/defs.h | 2 +- pkg/noun/events.c | 22 +++++++++++----------- pkg/noun/hashtable.c | 2 +- pkg/noun/retrieve.c | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/c3/defs.h b/pkg/c3/defs.h index aef4b6f93c..a98e66bca6 100644 --- a/pkg/c3/defs.h +++ b/pkg/c3/defs.h @@ -110,7 +110,7 @@ inline c3_w c3_sift_word(c3_y buf_y[4]) { - return (buf_y[3] << 24 | buf_y[2] << 16 | buf_y[1] << 8 | buf_y[0]); + return ((c3_w)buf_y[3] << 24 | (c3_w)buf_y[2] << 16 | (c3_w)buf_y[1] << 8 | (c3_w)buf_y[0]); } inline c3_d diff --git a/pkg/noun/events.c b/pkg/noun/events.c index 66db0fef98..6027218f36 100644 --- a/pkg/noun/events.c +++ b/pkg/noun/events.c @@ -292,7 +292,7 @@ u3e_fault(u3_post low_p, u3_post hig_p, u3_post off_p) return fal_e; } - if ( !(u3P.dit_w[blk_w] & (1 << bit_w)) ) { + if ( !(u3P.dit_w[blk_w] & ((c3_w)1 << bit_w)) ) { fprintf(stderr, "loom: strange guard (%d)\r\n", pag_w); return u3e_flaw_sham; } @@ -673,7 +673,7 @@ _ce_patch_count_page(c3_w pag_w, c3_w blk_w = (pag_w >> 5); c3_w bit_w = (pag_w & 31); - if ( u3P.dit_w[blk_w] & (1 << bit_w) ) { + if ( u3P.dit_w[blk_w] & ((c3_w)1 << bit_w) ) { pgc_w += 1; } return pgc_w; @@ -689,7 +689,7 @@ _ce_patch_save_page(u3_ce_patch* pat_u, c3_w blk_w = (pag_w >> 5); c3_w bit_w = (pag_w & 31); - if ( u3P.dit_w[blk_w] & (1 << bit_w) ) { + if ( u3P.dit_w[blk_w] & ((c3_w)1 << bit_w) ) { c3_w* mem_w = _ce_ptr(pag_w); pat_u->con_u->mem_u[pgc_w].pag_w = pag_w; @@ -904,7 +904,7 @@ _ce_loom_track_sane(void) blk_w = i_w >> 5; bit_w = i_w & 31; - if ( u3P.dit_w[blk_w] & (1 << bit_w) ) { + if ( u3P.dit_w[blk_w] & ((c3_w)1 << bit_w) ) { fprintf(stderr, "loom: insane north %u\r\n", i_w); san_o = c3n; } @@ -916,7 +916,7 @@ _ce_loom_track_sane(void) blk_w = i_w >> 5; bit_w = i_w & 31; - if ( !(u3P.dit_w[blk_w] & (1 << bit_w)) ) { + if ( !(u3P.dit_w[blk_w] & ((c3_w)1 << bit_w)) ) { fprintf(stderr, "loom: insane open %u\r\n", i_w); san_o = c3n; } @@ -928,7 +928,7 @@ _ce_loom_track_sane(void) blk_w = i_w >> 5; bit_w = i_w & 31; - if ( u3P.dit_w[blk_w] & (1 << bit_w) ) { + if ( u3P.dit_w[blk_w] & ((c3_w)1 << bit_w) ) { fprintf(stderr, "loom: insane south %u\r\n", i_w); san_o = c3n; } @@ -955,7 +955,7 @@ _ce_loom_track_north(c3_w pgs_w, c3_w dif_w) for ( ; i_w < max_w; i_w++ ) { blk_w = i_w >> 5; bit_w = i_w & 31; - u3P.dit_w[blk_w] |= (1 << bit_w); + u3P.dit_w[blk_w] |= ((c3_w)1 << bit_w); } } @@ -977,7 +977,7 @@ _ce_loom_track_south(c3_w pgs_w, c3_w dif_w) for ( ; i_w >= max_w; i_w-- ) { blk_w = i_w >> 5; bit_w = i_w & 31; - u3P.dit_w[blk_w] |= (1 << bit_w); + u3P.dit_w[blk_w] |= ((c3_w)1 << bit_w); } } @@ -1260,7 +1260,7 @@ _ce_loom_fine(void) blk_w = pag_w >> 5; bit_w = pag_w & 31; - if ( !(u3P.dit_w[blk_w] & (1 << bit_w)) ) { + if ( !(u3P.dit_w[blk_w] & ((c3_w)1 << bit_w)) ) { fin_o = c3a(fin_o, _ce_page_fine(&u3P.nor_u, pag_w, _ce_len(pag_w))); } } @@ -1270,7 +1270,7 @@ _ce_loom_fine(void) blk_w = pag_w >> 5; bit_w = pag_w & 31; - if ( !(u3P.dit_w[blk_w] & (1 << bit_w)) ) { + if ( !(u3P.dit_w[blk_w] & ((c3_w)1 << bit_w)) ) { fin_o = c3a(fin_o, _ce_page_fine(&u3P.sou_u, pag_w, _ce_len(i_w))); } } @@ -1756,7 +1756,7 @@ u3e_ward(u3_post low_p, u3_post hig_p) if ( !((pag_w > nop_w) && (pag_w < sop_w)) ) { u3_assert( !_ce_ward_post(nop_w, sop_w) ); u3_assert( !_ce_flaw_mprotect(pag_w) ); - u3_assert( u3P.dit_w[pag_w >> 5] & (1 << (pag_w & 31)) ); + u3_assert( u3P.dit_w[pag_w >> 5] & ((c3_w)1 << (pag_w & 31)) ); } #endif } diff --git a/pkg/noun/hashtable.c b/pkg/noun/hashtable.c index cb817890cb..145464e7af 100644 --- a/pkg/noun/hashtable.c +++ b/pkg/noun/hashtable.c @@ -188,7 +188,7 @@ _ch_two(u3h_slot had_w, u3h_slot add_w, c3_w lef_w, c3_w ham_w, c3_w mad_w) // fragments collide: store in a child node. u3h_node* han_u = _ch_node_new(1); ret = han_u; - han_u->map_w = 1 << hop_w; + han_u->map_w = (c3_w)1 << hop_w; ham_w = CUT_END(ham_w, lef_w); mad_w = CUT_END(mad_w, lef_w); han_u->sot_w[0] = _ch_two(had_w, add_w, lef_w, ham_w, mad_w); diff --git a/pkg/noun/retrieve.c b/pkg/noun/retrieve.c index 8f9847ff92..41648caea4 100644 --- a/pkg/noun/retrieve.c +++ b/pkg/noun/retrieve.c @@ -1431,7 +1431,7 @@ u3r_chop_bits(c3_g bif_g, hig_w ^= src_w[1] << fib_y; } - *dst_w ^= (hig_w & ((1 << wid_d) - 1)) << bit_g; + *dst_w ^= (hig_w & (((c3_d)1 << wid_d) - 1)) << bit_g; } } From 9bb21893c79cd9ff487b08a600534e4eb161e5b4 Mon Sep 17 00:00:00 2001 From: pkova Date: Thu, 26 Sep 2024 18:35:06 +0300 Subject: [PATCH 4/5] serial: remove unnecessary assertion --- pkg/noun/serial.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/noun/serial.c b/pkg/noun/serial.c index 70ced99a68..5dd6f595a5 100644 --- a/pkg/noun/serial.c +++ b/pkg/noun/serial.c @@ -660,8 +660,6 @@ u3s_cue_xeno_init_with(c3_d pre_d, c3_d siz_d) { u3_cue_xeno* sil_u; - u3_assert( &(u3H->rod_u) == u3R ); - sil_u = c3_calloc(sizeof(*sil_u)); ur_dict32_grow((ur_root_t*)0, &sil_u->dic_u, pre_d, siz_d); From 6cafe5faf5608c835149f79438aa2224a5bd5ae5 Mon Sep 17 00:00:00 2001 From: pkova Date: Thu, 26 Sep 2024 18:36:46 +0300 Subject: [PATCH 5/5] noun: align u3n_prog to 8 byte boundaries --- build.zig | 1 + pkg/noun/manage.c | 4 +++- pkg/noun/manage_v4.c | 30 ++++++++++++++++++++++++++++++ pkg/noun/manage_v4.h | 13 +++++++++++++ pkg/noun/nock.c | 35 +++++++++++++++++++++++------------ pkg/noun/version.h | 3 ++- 6 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 pkg/noun/manage_v4.c create mode 100644 pkg/noun/manage_v4.h diff --git a/build.zig b/build.zig index 2ac88948c0..1408769c25 100644 --- a/build.zig +++ b/build.zig @@ -701,6 +701,7 @@ fn build_single( "vortex_v2.c", "hashtable_v3.c", "manage_v3.c", + "manage_v4.c", "vortex.c", "xtract.c", "zave.c", diff --git a/pkg/noun/manage.c b/pkg/noun/manage.c index e5dbcab223..7456a2e382 100644 --- a/pkg/noun/manage.c +++ b/pkg/noun/manage.c @@ -3,6 +3,7 @@ #include "manage.h" #include "manage_v2.h" #include "manage_v3.h" +#include "manage_v4.h" #include #include @@ -613,7 +614,8 @@ _find_home(void) switch ( ver_w ) { case U3V_VER1: u3m_v2_migrate(); case U3V_VER2: u3m_v3_migrate(); - case U3V_VER3: { + case U3V_VER3: u3m_v4_migrate(); + case U3V_VER4: { mig_o = c3n; break; } diff --git a/pkg/noun/manage_v4.c b/pkg/noun/manage_v4.c new file mode 100644 index 0000000000..14bfb52b62 --- /dev/null +++ b/pkg/noun/manage_v4.c @@ -0,0 +1,30 @@ +/// @file + +#include "manage_v4.h" +#include "stdio.h" +#include "manage.h" +#include "allocate.h" +#include "vortex.h" +#include "options.h" + +/* u3m_v4_migrate: perform loom migration if necessary. +*/ +void +u3m_v4_migrate(void) +{ + fprintf(stderr, "loom: bytecode alignment migration running...\r\n"); + + c3_w* mem_w = u3_Loom + u3a_walign; + c3_w siz_w = c3_wiseof(u3v_home); + c3_w len_w = u3C.wor_i - u3a_walign; + c3_w* mat_w = c3_align(mem_w + len_w - siz_w, u3a_balign, C3_ALGLO); + + u3H = (void *)mat_w; + u3R = &u3H->rod_u; + + u3m_reclaim(); + + u3H->ver_w = U3V_VER4; + + fprintf(stderr, "loom: bytecode alignment migration done\r\n"); +} diff --git a/pkg/noun/manage_v4.h b/pkg/noun/manage_v4.h new file mode 100644 index 0000000000..1059f12626 --- /dev/null +++ b/pkg/noun/manage_v4.h @@ -0,0 +1,13 @@ +/// @file + +#ifndef U3_MANAGE_V4_H +#define U3_MANAGE_V4_H + + /** System management. + **/ + /* u3m_v3_migrate: perform memoization loom migration if necessary. + */ + void + u3m_v4_migrate(void); + +#endif /* ifndef U3_MANAGE_V3_H */ diff --git a/pkg/noun/nock.c b/pkg/noun/nock.c index eb459d17f7..f9814a395f 100644 --- a/pkg/noun/nock.c +++ b/pkg/noun/nock.c @@ -706,7 +706,11 @@ _n_prog_new(c3_w byc_w, c3_w cal_w, reb_w = (sizeof(u3j_rite) * reg_w), lib_w = (sizeof(u3_noun) * lit_w), meb_w = (sizeof(u3n_memo) * mem_w), - dat_w = byc_w + cab_w + reb_w + lib_w + meb_w; + pad_w = (8 - byc_w % 8) % 8, + pod_w = lit_w % 2, + ped_w = mem_w % 2, + dat_w = byc_w + cab_w + reb_w + lib_w + meb_w + pad_w + + (pod_w * sizeof(u3_noun)) + (ped_w * sizeof(u3n_memo)); u3n_prog* pog_u = u3a_malloc(sizeof(u3n_prog) + dat_w); pog_u->byc_u.own_o = c3y; @@ -714,13 +718,13 @@ _n_prog_new(c3_w byc_w, c3_w cal_w, pog_u->byc_u.ops_y = (c3_y*) _n_prog_dat(pog_u); pog_u->lit_u.len_w = lit_w; - pog_u->lit_u.non = (u3_noun*) (pog_u->byc_u.ops_y + pog_u->byc_u.len_w); + pog_u->lit_u.non = (u3_noun*) (pog_u->byc_u.ops_y + pog_u->byc_u.len_w + pad_w); pog_u->mem_u.len_w = mem_w; - pog_u->mem_u.sot_u = (u3n_memo*) (pog_u->lit_u.non + pog_u->lit_u.len_w); + pog_u->mem_u.sot_u = (u3n_memo*) (pog_u->lit_u.non + pog_u->lit_u.len_w + pod_w); pog_u->cal_u.len_w = cal_w; - pog_u->cal_u.sit_u = (u3j_site*) (pog_u->mem_u.sot_u + pog_u->mem_u.len_w); + pog_u->cal_u.sit_u = (u3j_site*) (pog_u->mem_u.sot_u + pog_u->mem_u.len_w + ped_w); pog_u->reg_u.len_w = reg_w; pog_u->reg_u.rit_u = (u3j_rite*) (pog_u->cal_u.sit_u + pog_u->cal_u.len_w); @@ -738,7 +742,10 @@ _n_prog_old(u3n_prog* sep_u) reb_w = sizeof(u3j_rite) * sep_u->reg_u.len_w, lib_w = sizeof(u3_noun) * sep_u->lit_u.len_w, meb_w = sizeof(u3n_memo) * sep_u->mem_u.len_w, - dat_w = cab_w + reb_w + lib_w + meb_w; + pod_w = sep_u->lit_u.len_w % 2, + ped_w = sep_u->mem_u.len_w % 2, + dat_w = cab_w + reb_w + lib_w + meb_w + + (pod_w * sizeof(u3_noun)) + (ped_w * sizeof(u3n_memo)); u3n_prog* pog_u = u3a_malloc(sizeof(u3n_prog) + dat_w); pog_u->byc_u.own_o = c3n; @@ -749,10 +756,10 @@ _n_prog_old(u3n_prog* sep_u) pog_u->lit_u.non = (u3_noun*) _n_prog_dat(pog_u); pog_u->mem_u.len_w = sep_u->mem_u.len_w; - pog_u->mem_u.sot_u = (u3n_memo*) (pog_u->lit_u.non + pog_u->lit_u.len_w); + pog_u->mem_u.sot_u = (u3n_memo*) (pog_u->lit_u.non + pog_u->lit_u.len_w + pod_w); pog_u->cal_u.len_w = sep_u->cal_u.len_w; - pog_u->cal_u.sit_u = (u3j_site*) (pog_u->mem_u.sot_u + pog_u->mem_u.len_w); + pog_u->cal_u.sit_u = (u3j_site*) (pog_u->mem_u.sot_u + pog_u->mem_u.len_w + ped_w); pog_u->reg_u.len_w = sep_u->reg_u.len_w; pog_u->reg_u.rit_u = (u3j_rite*) (pog_u->cal_u.sit_u + pog_u->cal_u.len_w); @@ -2036,7 +2043,7 @@ _n_kale(u3_noun a) return a; } -typedef struct { +typedef struct __attribute__((__packed__)) { u3n_prog* pog_u; c3_w ip_w; } burnframe; @@ -2888,12 +2895,13 @@ _cn_take_prog_cb(u3p(u3n_prog) pog_p) u3n_prog* gop_u; if ( c3y == pog_u->byc_u.own_o ) { + c3_w pad_w = (8 - pog_u->byc_u.len_w % 8) % 8; gop_u = _n_prog_new(pog_u->byc_u.len_w, pog_u->cal_u.len_w, pog_u->reg_u.len_w, pog_u->lit_u.len_w, pog_u->mem_u.len_w); - memcpy(gop_u->byc_u.ops_y, pog_u->byc_u.ops_y, pog_u->byc_u.len_w); + memcpy(gop_u->byc_u.ops_y, pog_u->byc_u.ops_y, pog_u->byc_u.len_w + pad_w); } else { gop_u = _n_prog_old(pog_u); @@ -2988,11 +2996,14 @@ _n_ream(u3_noun kev) c3_w i_w; u3n_prog* pog_u = u3to(u3n_prog, u3t(kev)); + c3_w pad_w = (8 - pog_u->byc_u.len_w % 8) % 8; + c3_w pod_w = pog_u->lit_u.len_w % 2; + c3_w ped_w = pog_u->mem_u.len_w % 2; // fix up pointers for loom portability pog_u->byc_u.ops_y = (c3_y*) _n_prog_dat(pog_u); - pog_u->lit_u.non = (u3_noun*) (pog_u->byc_u.ops_y + pog_u->byc_u.len_w); - pog_u->mem_u.sot_u = (u3n_memo*) (pog_u->lit_u.non + pog_u->lit_u.len_w); - pog_u->cal_u.sit_u = (u3j_site*) (pog_u->mem_u.sot_u + pog_u->mem_u.len_w); + pog_u->lit_u.non = (u3_noun*) (pog_u->byc_u.ops_y + pog_u->byc_u.len_w + pad_w); + pog_u->mem_u.sot_u = (u3n_memo*) (pog_u->lit_u.non + pog_u->lit_u.len_w + pod_w); + pog_u->cal_u.sit_u = (u3j_site*) (pog_u->mem_u.sot_u + pog_u->mem_u.len_w + ped_w); pog_u->reg_u.rit_u = (u3j_rite*) (pog_u->cal_u.sit_u + pog_u->cal_u.len_w); for ( i_w = 0; i_w < pog_u->cal_u.len_w; ++i_w ) { diff --git a/pkg/noun/version.h b/pkg/noun/version.h index 47e43ee336..9130d5f94e 100644 --- a/pkg/noun/version.h +++ b/pkg/noun/version.h @@ -9,7 +9,8 @@ typedef c3_w u3v_version; #define U3V_VER1 1 #define U3V_VER2 2 #define U3V_VER3 3 -#define U3V_VERLAT U3V_VER3 +#define U3V_VER4 4 +#define U3V_VERLAT U3V_VER4 /* PATCHES */